mirror of
https://github.com/koush/scrypted.git
synced 2026-02-05 07:02:13 +00:00
Compare commits
433 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea628a7130 | ||
|
|
ed498ae418 | ||
|
|
5b46036b2d | ||
|
|
7bd4f4053d | ||
|
|
f83cbfa5e7 | ||
|
|
8480713ec6 | ||
|
|
dcae7ce367 | ||
|
|
101d362260 | ||
|
|
73bdca1be6 | ||
|
|
c407fa0b9f | ||
|
|
d26c595fd6 | ||
|
|
38c00f5b9b | ||
|
|
ab4738973d | ||
|
|
ea065f506c | ||
|
|
4b7b66c96b | ||
|
|
0462ad228b | ||
|
|
45ac7f2f4e | ||
|
|
6618129e1d | ||
|
|
1a72eddcc8 | ||
|
|
1c9037dc35 | ||
|
|
2c5b79291f | ||
|
|
cd0ab104ea | ||
|
|
c6f4c1a669 | ||
|
|
9f28e38716 | ||
|
|
ba8f25fde3 | ||
|
|
9f27b2f382 | ||
|
|
96fa6af0fc | ||
|
|
eca5fbecdc | ||
|
|
8e0e2854e9 | ||
|
|
1eb9d938e7 | ||
|
|
095f80e1f9 | ||
|
|
da1f6118c8 | ||
|
|
5060748e9d | ||
|
|
25df4f8376 | ||
|
|
56fdff3545 | ||
|
|
2fbfe2cb65 | ||
|
|
32ede1f7fe | ||
|
|
1a2ec8ab4e | ||
|
|
53cab91b02 | ||
|
|
02a46a9202 | ||
|
|
69f4de66e9 | ||
|
|
aed6e0c446 | ||
|
|
432c178f29 | ||
|
|
bcf698daa3 | ||
|
|
347a957cd3 | ||
|
|
459b95a0e2 | ||
|
|
1c18129449 | ||
|
|
21274df881 | ||
|
|
ca243e79bb | ||
|
|
924394d365 | ||
|
|
23167da88b | ||
|
|
c1d48e1c6b | ||
|
|
7a22e17d84 | ||
|
|
75b2ff22ce | ||
|
|
edde093140 | ||
|
|
8622934c8b | ||
|
|
153cc3ed94 | ||
|
|
2ae6113750 | ||
|
|
4ec001c2a2 | ||
|
|
794ac6c8d2 | ||
|
|
8422ffe55a | ||
|
|
285c07e33e | ||
|
|
ef04398a79 | ||
|
|
6429ea718a | ||
|
|
5f9147e720 | ||
|
|
ea4922d8e5 | ||
|
|
70293ca827 | ||
|
|
878487180e | ||
|
|
f094903ed9 | ||
|
|
5117e217cf | ||
|
|
07c3f2832a | ||
|
|
977666bc3c | ||
|
|
2ec6760308 | ||
|
|
0dbe556835 | ||
|
|
c4f76df255 | ||
|
|
0bf0ec08ab | ||
|
|
1a2216a7de | ||
|
|
d868c3b3bb | ||
|
|
882709ea51 | ||
|
|
df249c554c | ||
|
|
fcbaeb1d1d | ||
|
|
c6dda05fa4 | ||
|
|
953b7812c5 | ||
|
|
7434a5c4ba | ||
|
|
b240a17bb0 | ||
|
|
aab0507805 | ||
|
|
3e091623a8 | ||
|
|
0871898385 | ||
|
|
eea7e4be32 | ||
|
|
8167ca85bb | ||
|
|
a09291114f | ||
|
|
39efe0d994 | ||
|
|
21f56216b0 | ||
|
|
8820bac571 | ||
|
|
47a683e385 | ||
|
|
17f367a373 | ||
|
|
fad0a520ca | ||
|
|
1f0d5dc3b9 | ||
|
|
a965f9b569 | ||
|
|
eb1a388f69 | ||
|
|
b2cf5ac3c7 | ||
|
|
ce10a49f0f | ||
|
|
5e31a0db96 | ||
|
|
8f1a673db5 | ||
|
|
7405476556 | ||
|
|
7dc86f59bf | ||
|
|
2d7cef600d | ||
|
|
5de0f8937b | ||
|
|
8e8d333ea2 | ||
|
|
d66a6317de | ||
|
|
49e3fc1438 | ||
|
|
fbe3daa072 | ||
|
|
670216135b | ||
|
|
ff903fa891 | ||
|
|
ebc6ede275 | ||
|
|
4de91d0673 | ||
|
|
3da1d00f6f | ||
|
|
4ff00a7753 | ||
|
|
f245fb257d | ||
|
|
b1a21a6037 | ||
|
|
0b9f3309a2 | ||
|
|
09b9b33bac | ||
|
|
21d020919a | ||
|
|
02d090cb94 | ||
|
|
817db34357 | ||
|
|
a3eda8cfba | ||
|
|
5a62fdc06b | ||
|
|
5ff8a65c4a | ||
|
|
719dfd2f24 | ||
|
|
7d28d1d9d4 | ||
|
|
aaa924b9b4 | ||
|
|
f69b93c9fa | ||
|
|
12be06adad | ||
|
|
f6fa28b584 | ||
|
|
fc1e5210a5 | ||
|
|
7601b8f0d0 | ||
|
|
b0557704b2 | ||
|
|
572883ed98 | ||
|
|
92927c8b93 | ||
|
|
11ae57b185 | ||
|
|
9f55f0b32a | ||
|
|
ef52e0a723 | ||
|
|
3df6af1fcd | ||
|
|
a283cfb429 | ||
|
|
3ae2dd769a | ||
|
|
3b916e7e20 | ||
|
|
d93f05a228 | ||
|
|
68183775db | ||
|
|
a8db883661 | ||
|
|
4a51caa281 | ||
|
|
c3148b8ed9 | ||
|
|
bc95a15f89 | ||
|
|
8954de3c93 | ||
|
|
cbfad097db | ||
|
|
c9e83c496c | ||
|
|
442e1883c5 | ||
|
|
f819e6d29c | ||
|
|
261c07f330 | ||
|
|
2328c9dd75 | ||
|
|
15639052c3 | ||
|
|
d91c7d89b2 | ||
|
|
fbdefbe06a | ||
|
|
832ee0180c | ||
|
|
a616e95c0e | ||
|
|
7ab9208203 | ||
|
|
9db6808e85 | ||
|
|
5d48760fd8 | ||
|
|
6ce2166e0a | ||
|
|
201dc30650 | ||
|
|
84bb7865fe | ||
|
|
ab1cd379a9 | ||
|
|
9208ca9566 | ||
|
|
e62897e14c | ||
|
|
65559e6685 | ||
|
|
611b7c50bf | ||
|
|
e983526455 | ||
|
|
10c167d4a3 | ||
|
|
0c641ccf6c | ||
|
|
5899ad866a | ||
|
|
17ecb56259 | ||
|
|
ffeade08ca | ||
|
|
49a567fb51 | ||
|
|
aac104f386 | ||
|
|
b4aff117ce | ||
|
|
13d4519a35 | ||
|
|
743102c965 | ||
|
|
315e5bb6e6 | ||
|
|
6ddef853ad | ||
|
|
5848cf1e5e | ||
|
|
f00f650b4f | ||
|
|
e9d73c6faa | ||
|
|
b6d601ebc4 | ||
|
|
1b58b0dd9b | ||
|
|
1b5ef3103e | ||
|
|
78236a54b8 | ||
|
|
ec2e4d64fd | ||
|
|
44644448f5 | ||
|
|
0a86d5c4ea | ||
|
|
20282e05ea | ||
|
|
9d6b405fa9 | ||
|
|
b82ce5ff45 | ||
|
|
f461198e1e | ||
|
|
7505e6907a | ||
|
|
c1046d5706 | ||
|
|
a61c06b607 | ||
|
|
d3df5742e6 | ||
|
|
68ac42ca46 | ||
|
|
bb7c6ef8b9 | ||
|
|
446e8ed61e | ||
|
|
80372b35f2 | ||
|
|
57eff2f296 | ||
|
|
d996088041 | ||
|
|
04be70019b | ||
|
|
51732d0dcd | ||
|
|
e40bc3ddee | ||
|
|
3f4409e1c3 | ||
|
|
63b7616ab3 | ||
|
|
29059691ce | ||
|
|
531a9d28dc | ||
|
|
3314b4d9ca | ||
|
|
37df9810c8 | ||
|
|
47c1cbba3c | ||
|
|
ded7e549bb | ||
|
|
abb2b85cec | ||
|
|
7d157d2882 | ||
|
|
c6c0a225dd | ||
|
|
276fc386ec | ||
|
|
0b21afd193 | ||
|
|
1032e58e3b | ||
|
|
4987b01167 | ||
|
|
28bb8c5b3c | ||
|
|
2160170c3a | ||
|
|
c0eac9053b | ||
|
|
d57501dd42 | ||
|
|
264cb0404f | ||
|
|
dc9f4b39a8 | ||
|
|
653eeceaf2 | ||
|
|
3d8711947a | ||
|
|
38038d5f30 | ||
|
|
e21d9c3a0c | ||
|
|
7b8c014b3b | ||
|
|
55a30864fd | ||
|
|
4f419ff75c | ||
|
|
638a4f28ad | ||
|
|
8970154b8f | ||
|
|
c96debaaed | ||
|
|
fe7b479235 | ||
|
|
aa1486e641 | ||
|
|
207f00733e | ||
|
|
bc4b2c956c | ||
|
|
e15578c9ca | ||
|
|
e2c5ee2400 | ||
|
|
dec62ddbc6 | ||
|
|
e7a65c4a28 | ||
|
|
127855c57e | ||
|
|
d9f35ef461 | ||
|
|
787c452105 | ||
|
|
3556b326f5 | ||
|
|
dd42878a5f | ||
|
|
07db378763 | ||
|
|
df142635b8 | ||
|
|
24bcc32532 | ||
|
|
5856ad60dd | ||
|
|
124da3c1b7 | ||
|
|
005efbfe82 | ||
|
|
cb955f403d | ||
|
|
fa38d8c560 | ||
|
|
0e4a94cd6e | ||
|
|
a0c3721140 | ||
|
|
7efffe4c51 | ||
|
|
2ff80780f8 | ||
|
|
9a41e3bc29 | ||
|
|
23de4011fb | ||
|
|
4fba5b9484 | ||
|
|
0dcfa367a6 | ||
|
|
8e63943f53 | ||
|
|
e2d78611c5 | ||
|
|
8a94c268ac | ||
|
|
10bdef414e | ||
|
|
317ee80477 | ||
|
|
851c5caf4d | ||
|
|
03cfeffca5 | ||
|
|
196316ad97 | ||
|
|
f7ccdf0795 | ||
|
|
64d36513a0 | ||
|
|
6ae8fd6d96 | ||
|
|
7448f19b78 | ||
|
|
35f0e325c1 | ||
|
|
579e188d06 | ||
|
|
09e97a2895 | ||
|
|
700856bc2e | ||
|
|
26eb69b08a | ||
|
|
332d9716f9 | ||
|
|
511f348cbe | ||
|
|
f26b6c3bca | ||
|
|
d38abcd563 | ||
|
|
b71f6e8b7e | ||
|
|
b492e8912e | ||
|
|
e663f5d3fc | ||
|
|
21ac653c66 | ||
|
|
e6f80be974 | ||
|
|
7b41e17981 | ||
|
|
530961179d | ||
|
|
7bb1d75905 | ||
|
|
337a74a170 | ||
|
|
e21facb123 | ||
|
|
15ac061311 | ||
|
|
f66a300082 | ||
|
|
24d754b5dc | ||
|
|
0221d00a17 | ||
|
|
3c49501c2c | ||
|
|
a81e5337f0 | ||
|
|
675339f495 | ||
|
|
48c6b90e21 | ||
|
|
ccf2b8c67d | ||
|
|
cc97d7d1c1 | ||
|
|
6af2f5eecd | ||
|
|
1df4c964d7 | ||
|
|
24b5b36a44 | ||
|
|
1da7d25235 | ||
|
|
d691b21646 | ||
|
|
d691bfcef1 | ||
|
|
04d5633690 | ||
|
|
dffd20f978 | ||
|
|
5fd9579423 | ||
|
|
bd7d49833f | ||
|
|
701ca5e1e2 | ||
|
|
14cb5a2117 | ||
|
|
adf4e797c1 | ||
|
|
a946be88d3 | ||
|
|
4be844f3f7 | ||
|
|
9eff813619 | ||
|
|
e1b5eaa63f | ||
|
|
b7f1efc307 | ||
|
|
12fedc53ee | ||
|
|
1c7626c156 | ||
|
|
a578623d3f | ||
|
|
b13d32f3ed | ||
|
|
d34c3aae74 | ||
|
|
c27176f0f8 | ||
|
|
be4f4bb6d6 | ||
|
|
26db89b811 | ||
|
|
6f0738ef07 | ||
|
|
1ae1eafddc | ||
|
|
0ecacfd974 | ||
|
|
146a648f39 | ||
|
|
7015f26eee | ||
|
|
a4e484698d | ||
|
|
1d659a5fb9 | ||
|
|
80e70b6bb8 | ||
|
|
ceec1174e3 | ||
|
|
b658202d8a | ||
|
|
425ee41ab0 | ||
|
|
3f7b801ffb | ||
|
|
ccb7ae0323 | ||
|
|
252c90fb46 | ||
|
|
2beceaf869 | ||
|
|
1dbb9a0f63 | ||
|
|
85c35f5fb7 | ||
|
|
8966cd7c70 | ||
|
|
60aeae5336 | ||
|
|
eef6c3ed3f | ||
|
|
979c430303 | ||
|
|
4892b72f37 | ||
|
|
3fd81b86d7 | ||
|
|
b3a2789a1d | ||
|
|
fde5cfa51e | ||
|
|
1ada7bb3fe | ||
|
|
2445ea909d | ||
|
|
7b16e8e969 | ||
|
|
1ffe8357e3 | ||
|
|
c96b4730de | ||
|
|
d89a8c6072 | ||
|
|
8e47bbb153 | ||
|
|
b04bb414d6 | ||
|
|
bd947fb934 | ||
|
|
a643960863 | ||
|
|
83f869988d | ||
|
|
1fc0934b4e | ||
|
|
29b7e4151a | ||
|
|
ad3c4f9529 | ||
|
|
e9026372c1 | ||
|
|
9b30064896 | ||
|
|
c73ffb30c1 | ||
|
|
bae12eecae | ||
|
|
1440b3811d | ||
|
|
59a3a97577 | ||
|
|
07f02fe371 | ||
|
|
cf4f4b7c73 | ||
|
|
aab0bdfc41 | ||
|
|
438f61b729 | ||
|
|
0b3717439f | ||
|
|
a9a145bd3e | ||
|
|
2f97b39cbb | ||
|
|
8bc1fe1588 | ||
|
|
90740f3c9d | ||
|
|
f92a12b99c | ||
|
|
405453da03 | ||
|
|
d01fe4310b | ||
|
|
6bcab63e0e | ||
|
|
c2bf7a62ab | ||
|
|
bc631d6c8b | ||
|
|
66877d1efa | ||
|
|
96a4a11503 | ||
|
|
bd0393305b | ||
|
|
9033eadafb | ||
|
|
c42b8ecda2 | ||
|
|
521bb62f10 | ||
|
|
8819f0a249 | ||
|
|
341cfa1e2f | ||
|
|
96335544ad | ||
|
|
ce0adb5706 | ||
|
|
22fb257214 | ||
|
|
674034fb7e | ||
|
|
44dcfe5e12 | ||
|
|
194e8532c3 | ||
|
|
797ef79080 | ||
|
|
56dbecbf3d | ||
|
|
67acb7725f | ||
|
|
a2caad7109 | ||
|
|
599d370f7d | ||
|
|
be284a0df7 | ||
|
|
030c3432a7 | ||
|
|
7d0d26ad31 | ||
|
|
6ba8a1dea4 | ||
|
|
cafb6944f5 | ||
|
|
f800401317 | ||
|
|
d672e2271d | ||
|
|
3d558e3119 | ||
|
|
c94945c6d5 | ||
|
|
82afc6d53f | ||
|
|
16ba10da21 | ||
|
|
6f5e0700a2 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -11,9 +11,6 @@
|
||||
[submodule "external/werift"]
|
||||
path = external/werift
|
||||
url = ../../koush/werift-webrtc
|
||||
[submodule "sdk/developer.scrypted.app"]
|
||||
path = sdk/developer.scrypted.app
|
||||
url = ../../koush/developer.scrypted.app
|
||||
[submodule "plugins/sample-cameraprovider"]
|
||||
path = plugins/sample-cameraprovider
|
||||
url = ../../koush/scrypted-sample-cameraprovider
|
||||
@@ -23,6 +20,3 @@
|
||||
[submodule "plugins/wyze/docker-wyze-bridge"]
|
||||
path = plugins/wyze/docker-wyze-bridge
|
||||
url = ../../koush/docker-wyze-bridge.git
|
||||
[submodule "plugins/onvif/onvif"]
|
||||
path = plugins/onvif/onvif
|
||||
url = ../../koush/onvif.git
|
||||
|
||||
@@ -40,7 +40,7 @@ export function createAsyncQueue<T>() {
|
||||
return false;
|
||||
|
||||
if (waiting.length) {
|
||||
const deferred = waiting.shift();
|
||||
const deferred = waiting.shift()!;
|
||||
dequeued?.resolve();
|
||||
deferred.resolve(item);
|
||||
return true;
|
||||
@@ -66,7 +66,7 @@ export function createAsyncQueue<T>() {
|
||||
dequeued?.reject(new Error('abort'));
|
||||
};
|
||||
|
||||
dequeued.promise.catch(() => {}).finally(() => signal.removeEventListener('abort', h));
|
||||
dequeued?.promise.catch(() => {}).finally(() => signal.removeEventListener('abort', h));
|
||||
signal.addEventListener('abort', h);
|
||||
|
||||
return true;
|
||||
@@ -79,7 +79,7 @@ export function createAsyncQueue<T>() {
|
||||
ended = e || new EndError();
|
||||
endDeferred.resolve();
|
||||
while (waiting.length) {
|
||||
waiting.shift().reject(ended);
|
||||
waiting.shift()!.reject(ended);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export function createAsyncQueueFromGenerator<T>(generator: AsyncGenerator<T>) {
|
||||
(async() => {
|
||||
try {
|
||||
for await (const i of generator) {
|
||||
q.submit(i);
|
||||
await q.enqueue(i);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@@ -41,11 +41,15 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
checkHasEnabledMixin(device: ScryptedDevice) {
|
||||
return this.hasEnabledMixin[device.id] === this.autoIncludeToken;
|
||||
}
|
||||
|
||||
async maybeEnableMixin(device: ScryptedDevice) {
|
||||
if (!device || device.mixins?.includes(this.id))
|
||||
return;
|
||||
|
||||
if (this.hasEnabledMixin[device.id] === this.autoIncludeToken)
|
||||
if (this.checkHasEnabledMixin(device))
|
||||
return;
|
||||
|
||||
const match = await this.canMixin(device.type, device.interfaces);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import sdk, { MixinDeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes } from "@scrypted/sdk";
|
||||
import sdk, { LockState, MixinDeviceBase, PanTiltZoomMovement, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes } from "@scrypted/sdk";
|
||||
import { SettingsMixinDeviceBase } from "@scrypted/sdk/settings-mixin";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import fs from 'fs';
|
||||
@@ -76,6 +76,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
localStorage: device.storage,
|
||||
device,
|
||||
exports: {} as any,
|
||||
PanTiltZoomMovement,
|
||||
SettingsMixinDeviceBase,
|
||||
ScryptedMimeTypes,
|
||||
ScryptedInterface,
|
||||
|
||||
@@ -4,6 +4,41 @@ import os from 'os';
|
||||
|
||||
export type Zygote<T> = () => PluginFork<T>;
|
||||
|
||||
export function createService<T, V>(options: ForkOptions, create: (t: Promise<T>) => Promise<V>): {
|
||||
getResult: () => Promise<V>,
|
||||
terminate: () => void,
|
||||
} {
|
||||
let killed = false;
|
||||
let currentResult: Promise<V>;
|
||||
let currentFork: ReturnType<typeof sdk.fork<T>>;
|
||||
|
||||
return {
|
||||
getResult() {
|
||||
if (killed)
|
||||
throw new Error('service terminated');
|
||||
|
||||
if (currentResult)
|
||||
return currentResult;
|
||||
|
||||
currentFork = sdk.fork<T>(options);
|
||||
currentFork.worker.on('exit', () => currentResult = undefined);
|
||||
currentResult = create(currentFork.result);
|
||||
currentResult.catch(() => currentResult = undefined);
|
||||
return currentResult;
|
||||
},
|
||||
|
||||
terminate() {
|
||||
if (killed)
|
||||
return;
|
||||
|
||||
killed = true;
|
||||
currentFork.worker.terminate();
|
||||
currentFork = undefined;
|
||||
currentResult = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createZygote<T>(options?: ForkOptions): Zygote<T> {
|
||||
let zygote = sdk.fork<T>(options);
|
||||
function* next() {
|
||||
|
||||
2
external/ring-client-api
vendored
2
external/ring-client-api
vendored
Submodule external/ring-client-api updated: 3797e311ed...d9f51b8b6d
@@ -1,6 +1,6 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "v0.116.0-jammy-full"
|
||||
version: "v0.120.0-jammy-full"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
|
||||
@@ -18,4 +18,4 @@ ENV NODE_OPTIONS="--dns-result-order=ipv4first"
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20240321"
|
||||
|
||||
CMD npm --prefix /server exec scrypted-serve
|
||||
CMD ["/bin/sh", "-c", "ulimit -c 0; exec npm --prefix /server exec scrypted-serve"]
|
||||
|
||||
@@ -14,12 +14,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
# base tools and development stuff
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl software-properties-common apt-utils \
|
||||
build-essential \
|
||||
cmake \
|
||||
ffmpeg \
|
||||
gcc \
|
||||
libcairo2-dev \
|
||||
libgirepository1.0-dev \
|
||||
pkg-config && \
|
||||
apt-get -y update && \
|
||||
apt-get -y upgrade
|
||||
@@ -40,16 +35,12 @@ RUN apt-get -y install \
|
||||
python3-setuptools \
|
||||
python3-wheel
|
||||
|
||||
# these are necessary for pillow-simd, additional on disk size is small
|
||||
# but could consider removing this.
|
||||
RUN echo "Installing pillow-simd dependencies."
|
||||
RUN apt-get -y install \
|
||||
libjpeg-dev zlib1g-dev
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN echo "Installing gstreamer."
|
||||
# python-codecs pygobject dependencies
|
||||
RUN apt-get -y install libcairo2-dev libgirepository1.0-dev
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav \
|
||||
gstreamer1.0-vaapi
|
||||
|
||||
# python3 gstreamer bindings
|
||||
@@ -71,10 +62,18 @@ RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
################################################################
|
||||
FROM header as base
|
||||
|
||||
# intel opencl gpu and npu for openvino
|
||||
# vulkan
|
||||
RUN apt -y install libvulkan1
|
||||
|
||||
# intel opencl for openvino
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
|
||||
|
||||
# NPU driver will SIGILL on openvino prior to 2024.5.0
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
|
||||
|
||||
# amd opencl
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-amd-graphics.sh | bash
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
|
||||
FROM $BASE
|
||||
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES=all
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||
|
||||
# nvidia cudnn/libcublas etc.
|
||||
# for some reason this is not provided by the nvidia container toolkit
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-graphics.sh | bash
|
||||
|
||||
@@ -48,4 +48,4 @@ ENV NODE_OPTIONS="--dns-result-order=ipv4first"
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20240321"
|
||||
|
||||
CMD npm --prefix /server exec scrypted-serve
|
||||
CMD ["/bin/sh", "-c", "ulimit -c 0; exec npm --prefix /server exec scrypted-serve"]
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
services:
|
||||
scrypted:
|
||||
# LXC usage only
|
||||
# lxc privileged: true
|
||||
|
||||
environment:
|
||||
# Scrypted NVR Storage (Part 2 of 3)
|
||||
|
||||
@@ -29,30 +32,26 @@ services:
|
||||
# section below.
|
||||
# - SCRYPTED_NVR_VOLUME=/nvr
|
||||
|
||||
- SCRYPTED_WEBHOOK_UPDATE_AUTHORIZATION=Bearer SET_THIS_TO_SOME_RANDOM_TEXT
|
||||
- SCRYPTED_WEBHOOK_UPDATE_AUTHORIZATION=Bearer ${WATCHTOWER_HTTP_API_TOKEN:-env_missing_fallback}
|
||||
- SCRYPTED_WEBHOOK_UPDATE=http://localhost:10444/v1/update
|
||||
|
||||
# LXC usage only
|
||||
# lxc - SCRYPTED_INSTALL_ENVIRONMENT=lxc-docker
|
||||
|
||||
# Avahi can be used for network discovery by passing in the host daemon
|
||||
# or running the daemon inside the container. Choose one or the other.
|
||||
# Uncomment next line to run avahi-daemon inside the container.
|
||||
# See volumes and security_opt section below to use the host daemon.
|
||||
# - SCRYPTED_DOCKER_AVAHI=true
|
||||
|
||||
# NVIDIA (Part 1 of 4)
|
||||
# - NVIDIA_VISIBLE_DEVICES=all
|
||||
# - NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
# NVIDIA (Part 2 of 4)
|
||||
# NVIDIA (Part 1 of 2)
|
||||
# runtime: nvidia
|
||||
|
||||
# NVIDIA (Part 3 of 4) - Use NVIDIA image, and remove subsequent default image.
|
||||
# NVIDIA (Part 2 of 2) - Use NVIDIA image, and remove subsequent default image.
|
||||
# image: ghcr.io/koush/scrypted:nvidia
|
||||
image: ghcr.io/koush/scrypted
|
||||
|
||||
volumes:
|
||||
# NVIDIA (Part 4 of 4)
|
||||
# - /etc/OpenCL/vendors/nvidia.icd:/etc/OpenCL/vendors/nvidia.icd
|
||||
|
||||
# Scrypted NVR Storage (Part 3 of 3)
|
||||
|
||||
# Modify to add the additional volume for Scrypted NVR.
|
||||
@@ -77,6 +76,14 @@ services:
|
||||
|
||||
# Default volume for the Scrypted database. Typically should not be changed.
|
||||
- ~/.scrypted/volume:/server/volume
|
||||
|
||||
# LXC usage only
|
||||
# lxc - /var/run/docker.sock:/var/run/docker.sock
|
||||
# lxc - /root/.scrypted/docker-compose.yml:/root/.scrypted/docker-compose.yml
|
||||
# lxc - /root/.scrypted/docker-compose.sh:/root/.scrypted/docker-compose.sh
|
||||
# lxc - /root/.scrypted/.env:/root/.scrypted/.env
|
||||
# lxc - /mnt:/mnt
|
||||
|
||||
# Uncomment the following lines to use Avahi daemon from the host
|
||||
# Without this, AppArmor will block the container's attempt to talk to Avahi via dbus
|
||||
# security_opt:
|
||||
@@ -91,6 +98,9 @@ services:
|
||||
# hardware accelerated video decoding, opencl, etc.
|
||||
# "/dev/dri:/dev/dri",
|
||||
|
||||
# AMD GPU
|
||||
# "/dev/kfd:/dev/kfd",
|
||||
|
||||
# uncomment below as necessary.
|
||||
# zwave usb serial device
|
||||
|
||||
@@ -120,12 +130,10 @@ services:
|
||||
# watchtower manages updates for Scrypted.
|
||||
watchtower:
|
||||
environment:
|
||||
- WATCHTOWER_HTTP_API_TOKEN=SET_THIS_TO_SOME_RANDOM_TEXT
|
||||
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_HTTP_API_TOKEN:-env_missing_fallback}
|
||||
- WATCHTOWER_HTTP_API_UPDATE=true
|
||||
- WATCHTOWER_SCOPE=scrypted
|
||||
# remove the following line to never allow docker to auto update.
|
||||
# this is not recommended.
|
||||
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=true
|
||||
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=${WATCHTOWER_HTTP_API_PERIODIC_POLLS:-true}
|
||||
image: containrrr/watchtower
|
||||
container_name: scrypted-watchtower
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# disable core dumps.
|
||||
# this doesn't disable core dumps on the scrypted service itself, only stuff run by init.
|
||||
ulimit -c 0
|
||||
|
||||
if [[ "${SCRYPTED_DOCKER_AVAHI}" != "true" ]]; then
|
||||
echo "SCRYPTED_DOCKER_AVAHI != true, won't manage dbus nor avahi-daemon" >/dev/stderr
|
||||
exit 0
|
||||
|
||||
36
install/docker/install-amd-graphics.sh
Normal file
36
install/docker/install-amd-graphics.sh
Normal file
@@ -0,0 +1,36 @@
|
||||
if [ "$(uname -m)" != "x86_64" ]
|
||||
then
|
||||
echo "AMD graphics will not be installed on this architecture."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
|
||||
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
|
||||
|
||||
# needs either ubuntu 22.0.4 or 24.04
|
||||
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
|
||||
then
|
||||
echo "AMD graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
distro="jammy"
|
||||
else
|
||||
distro="noble"
|
||||
fi
|
||||
|
||||
# https://amdgpu-install.readthedocs.io/en/latest/install-prereq.html#installing-the-installer-package
|
||||
|
||||
FILENAME="amdgpu-install_6.2.60202-1_all.deb"
|
||||
set -e
|
||||
mkdir -p /tmp/amd
|
||||
cd /tmp/amd
|
||||
curl -O -L http://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/$FILENAME
|
||||
apt -y install rsync
|
||||
dpkg -i $FILENAME
|
||||
amdgpu-install --usecase=opencl --no-dkms -y --accept-eula
|
||||
cd /tmp
|
||||
rm -rf /tmp/amd
|
||||
|
||||
@@ -30,20 +30,24 @@ apt-get -y install intel-media-va-driver-non-free &&
|
||||
apt-get -y dist-upgrade;
|
||||
|
||||
# manual installation
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.35.30872.22
|
||||
# these debs are seemingly ubuntu 22.04 only.
|
||||
|
||||
rm -rf /tmp/gpu && mkdir -p /tmp/gpu && cd /tmp/gpu
|
||||
|
||||
apt-get install -y ocl-icd-libopencl1
|
||||
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-core_1.0.17193.4_amd64.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-opencl_1.0.17193.4_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-level-zero-gpu-dbgsym_1.3.30049.6_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-level-zero-gpu_1.3.30049.6_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd-dbgsym_24.26.30049.6_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd_24.26.30049.6_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/libigdgmm12_22.3.20_amd64.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-core_1.0.17537.20_amd64.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-opencl_1.0.17537.20_amd64.deb
|
||||
#curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-dbgsym_1.3.30872.22_amd64.ddeb
|
||||
#curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1-dbgsym_1.3.30872.22_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1_1.3.30872.22_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu_1.3.30872.22_amd64.deb
|
||||
#curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-dbgsym_24.35.30872.22_amd64.ddeb
|
||||
#curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1-dbgsym_24.35.30872.22_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1_24.35.30872.22_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd_24.35.30872.22_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/libigdgmm12_22.5.0_amd64.deb
|
||||
|
||||
dpkg -i *.deb
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ fi
|
||||
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
|
||||
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
|
||||
|
||||
if [ -z "$UBUNTU_22_04" ]
|
||||
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
|
||||
then
|
||||
# proxmox is compatible with ubuntu 22.04, check for /etc/pve directory
|
||||
if [ -d "/etc/pve" ]
|
||||
@@ -23,6 +23,13 @@ then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
distro="22.04_amd64"
|
||||
else
|
||||
distro="24.04_amd64"
|
||||
fi
|
||||
|
||||
dpkg --purge --force-remove-reinstreq intel-driver-compiler-npu intel-fw-npu intel-level-zero-npu
|
||||
|
||||
# no errors beyond this point
|
||||
@@ -30,27 +37,23 @@ set -e
|
||||
|
||||
rm -rf /tmp/npu && mkdir -p /tmp/npu && cd /tmp/npu
|
||||
|
||||
# different npu downloads for ubuntu versions
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-driver-compiler-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
# firmware can only be installed on host. will cause problems inside container.
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
else
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-driver-compiler-npu_1.5.1.20240708-9842236399_ubuntu24.04_amd64.deb
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
|
||||
fi
|
||||
# level zero must also be installed
|
||||
LEVEL_ZERO_VERSION=1.18.5
|
||||
# https://github.com/oneapi-src/level-zero
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero_"$LEVEL_ZERO_VERSION"+u$distro.deb
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero-devel_"$LEVEL_ZERO_VERSION"+u$distro.deb
|
||||
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero_1.17.6+u22.04_amd64.deb
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero-devel_1.17.6+u22.04_amd64.deb
|
||||
# npu driver
|
||||
# https://github.com/intel/linux-npu-driver
|
||||
NPU_VERSION=1.10.0
|
||||
NPU_VERSION_DATE=20241107-11729849322
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-driver-compiler-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
|
||||
# firmware can only be installed on host. will cause problems inside container.
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-fw-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-level-zero-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
|
||||
|
||||
apt -y update
|
||||
apt -y install libtbb12
|
||||
|
||||
44
install/docker/install-nvidia-container-toolkit.sh
Normal file
44
install/docker/install-nvidia-container-toolkit.sh
Normal file
@@ -0,0 +1,44 @@
|
||||
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
|
||||
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
|
||||
|
||||
set -e
|
||||
|
||||
# Install CUDA for 22.04
|
||||
# https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=24.04&target_type=deb_network
|
||||
# Install CUDA for 24.04
|
||||
# https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=24.04&target_type=deb_network
|
||||
# Do not apt install nvidia-open, must use cuda-drivers.
|
||||
|
||||
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
|
||||
then
|
||||
echo "NVIDIA container toolkit can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
distro="ubuntu2204"
|
||||
else
|
||||
distro="ubuntu2404"
|
||||
fi
|
||||
|
||||
apt update -q \
|
||||
&& apt install -y wget \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& dpkg -i /cuda-keyring.deb;
|
||||
|
||||
# https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
||||
apt -y update
|
||||
apt -y install gpg
|
||||
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --yes --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
|
||||
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
|
||||
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
|
||||
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
|
||||
apt -y update
|
||||
# is there a way to get a versioned package automatically?
|
||||
apt -y install nvidia-utils-560
|
||||
apt -y install nvidia-container-toolkit
|
||||
|
||||
nvidia-ctk runtime configure --runtime=docker
|
||||
nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place
|
||||
systemctl restart docker
|
||||
@@ -1,14 +1,52 @@
|
||||
if [ "$(uname -m)" = "x86_64" ]
|
||||
then
|
||||
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
|
||||
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
|
||||
|
||||
# needs either ubuntu 22.0.4 or 24.04
|
||||
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
|
||||
then
|
||||
echo "NVIDIA graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
distro="ubuntu2204"
|
||||
else
|
||||
distro="ubuntu2404"
|
||||
fi
|
||||
|
||||
echo "Installing NVIDIA graphics packages."
|
||||
apt update -q \
|
||||
&& apt install -y wget \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& dpkg -i /cuda-keyring.deb \
|
||||
&& apt update -q \
|
||||
&& apt install -y cuda-nvcc-11-8 libcublas-11-8 libcudnn8 cuda-libraries-11-8 \
|
||||
&& apt install -y cuda-nvcc-12-4 libcublas-12-4 libcudnn8 cuda-libraries-12-4;
|
||||
exit $?
|
||||
&& apt install -y cuda-nvcc-12-6 libcublas-12-6 libcudnn9-cuda-12 cuda-libraries-12-6;
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "Error: NVIDIA graphics packages failed to install."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Update: the libnvidia-opencl.so.1 file is not present in the container image, it is
|
||||
# mounted via the nvidia container runtime. This is why the following check is commented out.
|
||||
# this file is present but for some reason the icd file is not created by nvidia runtime.
|
||||
# if [ ! -f "/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.1" ]
|
||||
# then
|
||||
# echo "Error: NVIDIA OpenCL library not found."
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# the container runtime doesn't mount this file for some reason. seems to be a bug.
|
||||
# https://github.com/NVIDIA/nvidia-container-toolkit/issues/682
|
||||
# but the contents are simply the .so file, which is a symlink the nvidia runtime
|
||||
# will mount in.
|
||||
mkdir -p /etc/OpenCL/vendors/
|
||||
echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd
|
||||
else
|
||||
echo "NVIDIA graphics will not be installed on this architecture."
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$SCRYPTED_LXC" ]
|
||||
then
|
||||
export SERVICE_USER="root"
|
||||
export SCRYPTED_NONINTERACTIVE="true"
|
||||
fi
|
||||
|
||||
if [ -z "$SERVICE_USER" ]
|
||||
then
|
||||
echo "Scrypted SERVICE_USER environment variable was not specified. Service will not be installed."
|
||||
@@ -7,6 +13,12 @@ then
|
||||
fi
|
||||
|
||||
function readyn() {
|
||||
if [ ! -z "$SCRYPTED_NONINTERACTIVE" ]
|
||||
then
|
||||
yn="y"
|
||||
return
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
@@ -33,6 +45,11 @@ systemctl disable scrypted.service 2> /dev/null
|
||||
USER_HOME=$(eval echo ~$SERVICE_USER)
|
||||
SCRYPTED_HOME=$USER_HOME/.scrypted
|
||||
mkdir -p $SCRYPTED_HOME
|
||||
# remove various things from a previous local install.
|
||||
rm -rf $SCRYPTED_HOME/node_modules
|
||||
rm -rf $SCRYPTED_HOME/install.json
|
||||
rm -rf $SCRYPTED_HOME/package.json
|
||||
rm -rf $SCRYPTED_HOME/package-lock.json
|
||||
|
||||
set -e
|
||||
cd $SCRYPTED_HOME
|
||||
@@ -46,13 +63,34 @@ then
|
||||
usermod -aG docker $SERVICE_USER
|
||||
fi
|
||||
|
||||
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum)
|
||||
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32)
|
||||
echo "WATCHTOWER_HTTP_API_TOKEN=$WATCHTOWER_HTTP_API_TOKEN" > $SCRYPTED_HOME/.env
|
||||
# remove the following line from .env to disable autoupdates.
|
||||
# this is not recommended.
|
||||
echo "WATCHTOWER_HTTP_API_PERIODIC_POLLS=true" >> $SCRYPTED_HOME/.env
|
||||
|
||||
DOCKER_COMPOSE_YML=$SCRYPTED_HOME/docker-compose.yml
|
||||
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml > $DOCKER_COMPOSE_YML
|
||||
echo "Created $DOCKER_COMPOSE_YML"
|
||||
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml | sed s/SET_THIS_TO_SOME_RANDOM_TEXT/"$(echo $RANDOM | md5sum | head -c 32)"/g > $DOCKER_COMPOSE_YML
|
||||
if [ -d /dev/dri ]
|
||||
|
||||
if [ -z "$SCRYPTED_LXC" ]
|
||||
then
|
||||
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/g' $DOCKER_COMPOSE_YML
|
||||
if [ -e /dev/dri ]
|
||||
then
|
||||
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/g' $DOCKER_COMPOSE_YML
|
||||
fi
|
||||
if [ -e /dev/kfd ]
|
||||
then
|
||||
sed -i 's/'#' "\/dev\/kfd/"\/dev\/kfd/g' $DOCKER_COMPOSE_YML
|
||||
fi
|
||||
else
|
||||
# uncomment lxc specific stuff
|
||||
sed -i 's/'#' lxc //g' $DOCKER_COMPOSE_YML
|
||||
# never restart, systemd will handle it
|
||||
sed -i 's/restart: unless-stopped/restart: no/g' $DOCKER_COMPOSE_YML
|
||||
|
||||
sudo systemctl stop apparmor || true
|
||||
sudo apt -y purge apparmor || true
|
||||
fi
|
||||
|
||||
readyn "Install avahi-daemon? This is the recommended for reliable HomeKit discovery and pairing."
|
||||
@@ -66,7 +104,7 @@ then
|
||||
fi
|
||||
|
||||
echo "Setting permissions on $SCRYPTED_HOME"
|
||||
chown -R $SERVICE_USER $SCRYPTED_HOME
|
||||
chown -R $SERVICE_USER $SCRYPTED_HOME || true
|
||||
|
||||
set +e
|
||||
|
||||
@@ -79,8 +117,41 @@ set -e
|
||||
|
||||
echo "docker compose pull"
|
||||
sudo -u $SERVICE_USER docker compose pull
|
||||
echo "docker compose up -d"
|
||||
sudo -u $SERVICE_USER docker compose up -d
|
||||
|
||||
if [ -z "$SCRYPTED_LXC" ]
|
||||
then
|
||||
echo "docker compose up -d"
|
||||
sudo -u $SERVICE_USER docker compose up -d
|
||||
else
|
||||
export DOCKER_COMPOSE_SH=$SCRYPTED_HOME/docker-compose.sh
|
||||
|
||||
curl https://raw.githubusercontent.com/koush/scrypted/main/install/proxmox/docker-compose.sh > $DOCKER_COMPOSE_SH
|
||||
|
||||
chmod +x $DOCKER_COMPOSE_SH
|
||||
|
||||
cat > /etc/systemd/system/scrypted.service <<EOT
|
||||
[Unit]
|
||||
Description=Scrypted service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
Type=simple
|
||||
ExecStart=$DOCKER_COMPOSE_SH
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOT
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable scrypted.service
|
||||
systemctl restart scrypted.service
|
||||
fi
|
||||
|
||||
echo
|
||||
echo
|
||||
@@ -91,5 +162,5 @@ echo "Note that it is https and that you'll be asked to approve/ignore the websi
|
||||
echo
|
||||
echo
|
||||
echo "Optional:"
|
||||
echo "Scrypted NVR Recording storage directory can be configured with an additional script:"
|
||||
echo "https://docs.scrypted.app/scrypted-nvr/installation.html#docker-volume"
|
||||
echo "Scrypted NVR Recording storage directory can be configured with an additional script located at:"
|
||||
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#docker-volume"
|
||||
|
||||
@@ -3,10 +3,18 @@
|
||||
################################################################
|
||||
FROM header as base
|
||||
|
||||
# intel opencl gpu and npu for openvino
|
||||
# vulkan
|
||||
RUN apt -y install libvulkan1
|
||||
|
||||
# intel opencl for openvino
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
|
||||
|
||||
# NPU driver will SIGILL on openvino prior to 2024.5.0
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
|
||||
|
||||
# amd opencl
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-amd-graphics.sh | bash
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
|
||||
|
||||
@@ -11,12 +11,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
# base tools and development stuff
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl software-properties-common apt-utils \
|
||||
build-essential \
|
||||
cmake \
|
||||
ffmpeg \
|
||||
gcc \
|
||||
libcairo2-dev \
|
||||
libgirepository1.0-dev \
|
||||
pkg-config && \
|
||||
apt-get -y update && \
|
||||
apt-get -y upgrade
|
||||
@@ -37,16 +32,12 @@ RUN apt-get -y install \
|
||||
python3-setuptools \
|
||||
python3-wheel
|
||||
|
||||
# these are necessary for pillow-simd, additional on disk size is small
|
||||
# but could consider removing this.
|
||||
RUN echo "Installing pillow-simd dependencies."
|
||||
RUN apt-get -y install \
|
||||
libjpeg-dev zlib1g-dev
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN echo "Installing gstreamer."
|
||||
# python-codecs pygobject dependencies
|
||||
RUN apt-get -y install libcairo2-dev libgirepository1.0-dev
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav \
|
||||
gstreamer1.0-vaapi
|
||||
|
||||
# python3 gstreamer bindings
|
||||
|
||||
@@ -40,8 +40,6 @@ echo "Installing Scrypted dependencies..."
|
||||
RUN_IGNORE xcode-select --install
|
||||
RUN brew update
|
||||
RUN_IGNORE brew install node@20
|
||||
# snapshot plugin and others
|
||||
RUN brew install libvips
|
||||
# dlib
|
||||
RUN brew install cmake
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
function readyn() {
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
[Yy]* ) break;;
|
||||
[Nn]* ) break;;
|
||||
* ) echo "Please answer yes or no. (y/n)";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.116.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
VMID=10443
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
RESTORE_VMID=$VMID
|
||||
VMID=10444
|
||||
pct destroy $VMID 2>&1 > /dev/null
|
||||
fi
|
||||
|
||||
echo "Downloading scrypted container backup."
|
||||
if [ ! -f "$SCRYPTED_TAR_ZST" ]
|
||||
then
|
||||
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
|
||||
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
|
||||
fi
|
||||
|
||||
echo "Checking for existing container."
|
||||
pct config $VMID
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "Existing container $VMID found. Run this script with --force to overwrite the existing container."
|
||||
echo "This will wipe all existing data. Clone the existing container to retain the data, then reassign the owner of the scrypted volume after installation is complete."
|
||||
echo ""
|
||||
echo "bash $0 --force"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
pct restore $VMID $SCRYPTED_TAR_ZST $@
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "The Scrypted container installation failed (pct restore error)."
|
||||
echo ""
|
||||
echo "This may be because the server's 'local' storage device is not being a valid"
|
||||
echo "location for containers."
|
||||
echo "Try running this script again with a different storage device like"
|
||||
echo "'local-lvm' or 'local-zfs'."
|
||||
echo ""
|
||||
echo "#############################################################################"
|
||||
echo "Paste the following command into this shell to install to local-lvm instead:"
|
||||
echo ""
|
||||
echo "bash $0 --storage local-lvm"
|
||||
echo "#############################################################################"
|
||||
echo ""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "pct set network failed"
|
||||
echo ""
|
||||
echo "Ignoring... Please verify your container's network settings."
|
||||
fi
|
||||
|
||||
CONF=/etc/pve/lxc/$VMID.conf
|
||||
if [ -f "$CONF" ]
|
||||
then
|
||||
echo "onboot: 1" >> $CONF
|
||||
else
|
||||
echo "$CONF not found? Start on boot must be enabled manually."
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
readyn "Running this script will reset Scrypted to a factory state while preserving existing data. IT IS RECOMMENDED TO CREATE A BACKUP FIRST. Are you sure you want to continue?"
|
||||
if [ "$yn" != "y" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Preparing rootfs reset..."
|
||||
# this copies the
|
||||
pct set 10444 --delete mp0 && pct set 10444 --delete unused0 && pct move-volume $RESTORE_VMID mp0 --target-vmid 10444 --target-volume mp0
|
||||
|
||||
rm *.tar
|
||||
vzdump 10444 --dumpdir /tmp
|
||||
VMID=$RESTORE_VMID
|
||||
echo "Moving data volume to backup..."
|
||||
pct restore $VMID *.tar $@
|
||||
|
||||
pct destroy 10444
|
||||
fi
|
||||
|
||||
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
|
||||
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"renderD128\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"card0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"accel0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
fi
|
||||
|
||||
echo "Scrypted setup is complete and the container resources can be started."
|
||||
echo "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."
|
||||
24
install/proxmox/docker-compose.sh
Normal file
24
install/proxmox/docker-compose.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
cd /root/.scrypted
|
||||
|
||||
# always immediately upgrade everything in case there's a broken update.
|
||||
# this will also be preferable for troubleshooting via lxc reboot.
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
(apt -y --fix-broken install && (yes | dpkg --configure -a) && apt -y update && apt -y dist-upgrade) &
|
||||
|
||||
# foreground pull if requested.
|
||||
if [ -e "volume/.pull" ]
|
||||
then
|
||||
rm -rf volume/.pull
|
||||
PULL="--pull"
|
||||
(sleep 300 && docker container prune -f && docker image prune -a -f) &
|
||||
else
|
||||
# always background pull in case there's a broken image.
|
||||
(sleep 300 && docker compose pull && docker container prune -f && docker image prune -a -f) &
|
||||
fi
|
||||
|
||||
# do not daemonize, when it exits, systemd will restart it.
|
||||
# force a recreate as .env may have changed.
|
||||
# furthermore force recreate gets the container back into a known state
|
||||
# which is preferable in case the user has made manual changes and then restarts.
|
||||
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate --abort-on-container-exit $PULL
|
||||
270
install/proxmox/install-scrypted-proxmox.sh
Normal file
270
install/proxmox/install-scrypted-proxmox.sh
Normal file
@@ -0,0 +1,270 @@
|
||||
PCT=$(which pct)
|
||||
if [ -z "$PCT" ]
|
||||
then
|
||||
echo "pct command not found. This script must be run on the Proxmox host, not a container."
|
||||
echo "Installation Documentation: https://docs.scrypted.app/installation.html#proxmox-ve"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function readyn() {
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
[Yy]* ) break;;
|
||||
[Nn]* ) break;;
|
||||
* ) echo "Please answer yes or no. (y/n)";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.120.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
VMID=10443
|
||||
fi
|
||||
|
||||
SCRYPTED_BACKUP_VMID=10445
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
pct config $VMID 2>&1 > /dev/null
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "VMID $VMID not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# append existing mac address.
|
||||
HWADDR=",hwaddr=$(pct config $VMID | grep -oE 'hwaddr=[A-Z0-9:]+' | cut -d '=' -f 2)"
|
||||
RESTORE_HOSTNAME=$(pct config $VMID | grep -oE 'hostname: [^[:space:]]+' | cut -d ':' -f 2- | tr -d ' ')
|
||||
|
||||
pct destroy $SCRYPTED_BACKUP_VMID 2>&1 > /dev/null
|
||||
RESTORE_VMID=$VMID
|
||||
VMID=$SCRYPTED_BACKUP_VMID
|
||||
pct destroy $VMID 2>&1 > /dev/null
|
||||
fi
|
||||
|
||||
echo "Downloading scrypted container backup."
|
||||
if [ ! -f "$SCRYPTED_TAR_ZST" ]
|
||||
then
|
||||
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
|
||||
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
|
||||
fi
|
||||
|
||||
if [[ "$@" =~ "--force" ]]
|
||||
then
|
||||
IGNORE_EXISTING=true
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
IGNORE_EXISTING=true
|
||||
fi
|
||||
|
||||
if [ -z "$IGNORE_EXISTING" ]
|
||||
then
|
||||
echo "Checking for existing container."
|
||||
pct config $VMID
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "==============================================================="
|
||||
echo "Existing container $VMID found."
|
||||
echo "Please choose from the following options to resolve this error."
|
||||
echo "==============================================================="
|
||||
echo ""
|
||||
echo "1. To reinstall and reset Scrypted, run this script with --force to overwrite the existing container."
|
||||
echo "THIS WILL WIPE THE EXISTING CONFIGURATION:"
|
||||
echo ""
|
||||
echo "VMID=$VMID bash $0 --force"
|
||||
echo ""
|
||||
echo "2. To reinstall Scrypted and and retain existing configuration, run this script with the environment variable SCRYPTED_RESTORE=true."
|
||||
echo "This preserves existing data. Creating a backup within Scrypted is highly recommended in case the reset fails."
|
||||
echo "THIS WILL WIPE ADDITIONAL VOLUMES SUCH AS NVR STORAGE. NVR volumes will need to be readded after the restore:"
|
||||
echo ""
|
||||
echo "SCRYPTED_RESTORE=true VMID=$VMID bash $0"
|
||||
echo ""
|
||||
echo "3. To install and run multiple Scrypted containers, run this script with the environment variable specifying"
|
||||
echo "the new VMID=<number>. For example, to create a new LXC with VMID 12345:"
|
||||
echo ""
|
||||
echo "VMID=12345 bash $0"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
pct stop $VMID 2>&1 > /dev/null
|
||||
pct restore $VMID $SCRYPTED_TAR_ZST $@
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "The Scrypted container installation failed (pct restore error)."
|
||||
echo ""
|
||||
echo "This may be because the server's 'local' storage device is not being a valid"
|
||||
echo "location for containers."
|
||||
echo "Try running this script again with a different storage device like"
|
||||
echo "'local-lvm' or 'local-zfs'."
|
||||
echo ""
|
||||
echo "#############################################################################"
|
||||
echo -e "\033[32mPaste the following command into this shell to install to local-lvm instead:\033[0m"
|
||||
echo ""
|
||||
echo "bash $0 --storage local-lvm"
|
||||
echo "#############################################################################"
|
||||
echo ""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto$HWADDR
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "pct set network failed"
|
||||
echo ""
|
||||
echo "Ignoring... Please verify your container's network settings."
|
||||
fi
|
||||
|
||||
if [ -n "$RESTORE_HOSTNAME" ]
|
||||
then
|
||||
pct set $VMID --hostname $RESTORE_HOSTNAME
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "pct hostname restore failed"
|
||||
echo ""
|
||||
echo "Ignoring... Please verify your container's dns settings."
|
||||
fi
|
||||
fi
|
||||
|
||||
CONF=/etc/pve/lxc/$VMID.conf
|
||||
if [ -f "$CONF" ]
|
||||
then
|
||||
echo "onboot: 1" >> $CONF
|
||||
else
|
||||
echo "$CONF not found? Start on boot must be enabled manually."
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
echo ""
|
||||
echo ""
|
||||
echo "Running this script will reset the Scrypted container to a factory state while preserving existing data."
|
||||
echo "IT IS RECOMMENDED TO CREATE A BACKUP INSIDE SCRYPTED FIRST."
|
||||
readyn "Are you sure you want to continue?"
|
||||
if [ "$yn" != "y" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Stopping scrypted..."
|
||||
pct stop $RESTORE_VMID 2>&1 > /dev/null
|
||||
|
||||
echo "Preparing rootfs reset..."
|
||||
|
||||
# remove the empty data volume from the downloaded image.
|
||||
pct set $SCRYPTED_BACKUP_VMID --delete mp0 && pct set $SCRYPTED_BACKUP_VMID --delete unused0
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "Failed to remove data volume from image."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# create a backup that contains only the root disk.
|
||||
rm -f *.tar
|
||||
vzdump $SCRYPTED_BACKUP_VMID --dumpdir /tmp
|
||||
|
||||
# this moves the data volume from the current scrypted instance to the backup target to preserve it during
|
||||
# the restore.
|
||||
pct move-volume $RESTORE_VMID mp0 --target-vmid $SCRYPTED_BACKUP_VMID --target-volume mp0
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "Failed to move data volume to backup."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# arguments: from to mp hide-warning
|
||||
function move_volume() {
|
||||
HAS_VOLUME=$(pct config $1 | grep $3:)
|
||||
if [ -n "$HAS_VOLUME" ]
|
||||
then
|
||||
echo "Moving $3..."
|
||||
# this may error and there may be recording loss. bailing at ths point is already too late.
|
||||
pct move-volume $1 $3 --target-vmid $2 --target-volume $3
|
||||
|
||||
# volume must be inside /mnt to get into docker container
|
||||
INSIDE_MNT=$(echo $HAS_VOLUME | grep /mnt)
|
||||
if [ -z "$INSIDE_MNT" -a -z "$4" ]
|
||||
then
|
||||
echo "##################################################################"
|
||||
echo "The following mount point is not visible to the"
|
||||
echo "Scrypted docker container within the LXC:"
|
||||
echo ""
|
||||
echo "$HAS_VOLUME"
|
||||
echo ""
|
||||
echo "This recordings directory will be unavailable."
|
||||
echo "The mount point must be updated to a path within /mnt."
|
||||
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#proxmox-ve-mount-point"
|
||||
echo "##################################################################"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# try moving 5 volumes, any more than that seems unlikely
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp1 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp2 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp3 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp4 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp5 hide-warning
|
||||
|
||||
VMID=$RESTORE_VMID
|
||||
echo "Restoring with reset image..."
|
||||
pct restore --force 1 $VMID *.tar $@
|
||||
|
||||
echo "Restoring volumes..."
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp0 hide-warning
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp1
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp2
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp3
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp4
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp5
|
||||
|
||||
pct destroy $SCRYPTED_BACKUP_VMID
|
||||
fi
|
||||
|
||||
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"drm\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"kfd\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"accel\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
fi
|
||||
|
||||
# check if intel
|
||||
INTEL=$(cat /proc/cpuinfo | grep GenuineIntel)
|
||||
if [ ! -z "$INTEL" ]
|
||||
then
|
||||
readyn "Install intel-microcode package? This will update your CPU and GPU firmware."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
echo "Installing intel-microcode..."
|
||||
# remove it first to allow reinsertion
|
||||
sed -i 's/main contrib non-free-firmware/main/g' /etc/apt/sources.list
|
||||
sed -i 's/main/main contrib non-free-firmware/g' /etc/apt/sources.list
|
||||
apt update
|
||||
apt install -y intel-microcode
|
||||
echo "#############################"
|
||||
echo "System Reboot is recommended."
|
||||
echo "#############################"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Scrypted setup is complete and the container resources can be started."
|
||||
echo ""
|
||||
echo "Scrypted NVR servers should run the disk setup script in the documentation to add storage prior to starting the container."
|
||||
BIN
install/proxmox/lxc.png
Normal file
BIN
install/proxmox/lxc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
74
install/proxmox/setup-scrypted-nvr-volume.sh
Normal file
74
install/proxmox/setup-scrypted-nvr-volume.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
|
||||
NVR_STORAGE=$1
|
||||
NVR_STORAGE_DIRECTORY=$2
|
||||
|
||||
DISK_TYPE="large"
|
||||
if [ ! -z "$FAST_DISK" ]
|
||||
then
|
||||
DISK_TYPE="fast"
|
||||
fi
|
||||
|
||||
if [ -z "$NVR_STORAGE" ]; then
|
||||
echo ""
|
||||
echo "Error: Directory name not provided. Usage:"
|
||||
echo ""
|
||||
echo "bash $0 directory-name [/optional/path/to/storage]"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
VMID="10443"
|
||||
fi
|
||||
FILE="/etc/pve/lxc/$VMID.conf"
|
||||
|
||||
# valdiate file exists
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "Error: $FILE not found."
|
||||
echo "If the Scrypted container id is not 10443, please set the VMID environment variable prior to running this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -z "$NVR_STORAGE_DIRECTORY" ]
|
||||
then
|
||||
if [ ! -d "$NVR_STORAGE_DIRECTORY" ]
|
||||
then
|
||||
echo ""
|
||||
echo "Error: $NVR_STORAGE_DIRECTORY directory not found."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
STORAGE="/mnt/pve/$NVR_STORAGE"
|
||||
if [ ! -d "$STORAGE" ]
|
||||
then
|
||||
echo "Error: $STORAGE not found."
|
||||
echo "The Proxmox Directory Storage must be created using the UI prior to running this script."
|
||||
exit 1
|
||||
fi
|
||||
# use subdirectory doesn't conflict with Proxmox storage of backups etc.
|
||||
NVR_STORAGE_DIRECTORY="$STORAGE/mounts/scrypted-nvr"
|
||||
fi
|
||||
|
||||
# create the hidden folder that can be used as a marker.
|
||||
mkdir -p $NVR_STORAGE_DIRECTORY/.nvr
|
||||
chmod 0777 $NVR_STORAGE_DIRECTORY
|
||||
|
||||
echo "Stopping Scrypted..."
|
||||
pct stop "$VMID"
|
||||
|
||||
echo "Modifying $FILE."
|
||||
|
||||
if [ -z "$ADD_DISK" ]
|
||||
then
|
||||
echo "Removing previous $DISK_TYPE lxc.mount.entry."
|
||||
sed -i "/mnt\/nvr\/$DISK_TYPE/d" "$FILE"
|
||||
fi
|
||||
|
||||
echo "Adding new $DISK_TYPE lxc.mount.entry."
|
||||
echo "lxc.mount.entry: $NVR_STORAGE_DIRECTORY mnt/nvr/$DISK_TYPE/$NVR_STORAGE none bind,optional,create=dir" >> "$FILE"
|
||||
|
||||
echo "Starting Scrypted..."
|
||||
pct start $VMID
|
||||
@@ -27,7 +27,7 @@ echo "external/werift > npm install"
|
||||
npm install
|
||||
popd
|
||||
|
||||
for directory in ffmpeg-camera rtsp amcrest onvif hikvision unifi-protect webrtc homekit
|
||||
for directory in rtsp amcrest onvif hikvision reolink unifi-protect webrtc homekit
|
||||
do
|
||||
echo "$directory > npm install"
|
||||
pushd plugins/$directory
|
||||
|
||||
157
packages/client/package-lock.json
generated
157
packages/client/package-lock.json
generated
@@ -9,16 +9,16 @@
|
||||
"version": "1.3.6",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.3.40",
|
||||
"engine.io-client": "^6.5.3",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"rimraf": "^5.0.5"
|
||||
"@scrypted/types": "^0.3.66",
|
||||
"engine.io-client": "^6.6.1",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"rimraf": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/node": "^22.7.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3"
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -74,19 +74,10 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.3.40",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.40.tgz",
|
||||
"integrity": "sha512-NBjNEfoLp7zL5Tf0odzf191oReDh4FEmZexDmMj1JbKDUMB9S8xJys3vbhcFadU/aUrUkyK/FSbkXv1z87bxSw=="
|
||||
"version": "0.3.66",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.66.tgz",
|
||||
"integrity": "sha512-POHpVgW6Ce8mnJRaXZRm+2RtvFuPP+ZehsDrhUqkQdxmnV81m8K2+3M6Vhrt+07kNDXmrznAijoj/OzXkdZWgw=="
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
@@ -127,12 +118,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
|
||||
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
@@ -157,9 +148,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -268,15 +259,15 @@
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.0.tgz",
|
||||
"integrity": "sha512-iBtCdW5Tk3CnMAnC44VO4LwxXnl+RIq9ua1qHvxf5KSq2rzFgQFdfCSSl6Yuz2hl899SWTkfaT3c+WZQ42dJ8A==",
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
|
||||
"integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
@@ -288,9 +279,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -307,9 +298,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
|
||||
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"signal-exit": "^4.0.1"
|
||||
@@ -322,21 +313,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.3.10",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
|
||||
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
|
||||
"integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.3.5",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
|
||||
"path-scurry": "^1.10.1"
|
||||
"jackspeak": "^4.0.1",
|
||||
"minimatch": "^10.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -356,28 +348,25 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
|
||||
"integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz",
|
||||
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
|
||||
"integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==",
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
@@ -387,23 +376,23 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
|
||||
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
|
||||
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
@@ -413,6 +402,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -422,32 +416,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
|
||||
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
|
||||
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^9.1.1 || ^10.0.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
|
||||
"integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
|
||||
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
|
||||
"dependencies": {
|
||||
"glob": "^10.3.7"
|
||||
"glob": "^11.0.0",
|
||||
"package-json-from-dist": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -615,9 +610,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
|
||||
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -628,9 +623,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
@@ -758,9 +753,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
|
||||
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/node": "^22.7.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3"
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.3.40",
|
||||
"engine.io-client": "^6.5.3",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"rimraf": "^5.0.5"
|
||||
"@scrypted/types": "^0.3.66",
|
||||
"engine.io-client": "^6.6.1",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"rimraf": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ export async function loginScryptedClient(options: ScryptedLoginOptions) {
|
||||
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
|
||||
directAddress: response.headers.get('x-scrypted-direct-address'),
|
||||
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
|
||||
serverId: response.headers.get('x-scrypted-server-id'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -213,6 +214,7 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
|
||||
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
|
||||
directAddress: response.headers.get('x-scrypted-direct-address'),
|
||||
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
|
||||
serverId: response.headers.get('x-scrypted-server-id'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -227,6 +229,7 @@ export interface ScryptedClientLoginResult {
|
||||
directAddress: string;
|
||||
cloudAddress: string;
|
||||
hostname: string;
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
export class ScryptedClientLoginError extends Error {
|
||||
@@ -274,6 +277,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
let cloudAddress: string;
|
||||
let hostname: string;
|
||||
let token: string;
|
||||
let serverId: string;
|
||||
|
||||
console.log('@scrypted/client', packageJson.version);
|
||||
|
||||
@@ -299,6 +303,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
queryToken = loginResult.queryToken;
|
||||
token = loginResult.token;
|
||||
hostname = loginResult.hostname;
|
||||
serverId = loginResult.serverId;
|
||||
console.log('login result', Date.now() - start, loginResult);
|
||||
}
|
||||
else {
|
||||
@@ -372,6 +377,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
queryToken = loginCheck.queryToken;
|
||||
token = loginCheck.token;
|
||||
hostname = loginCheck.hostname;
|
||||
serverId = loginCheck.serverId;
|
||||
console.log('login checked', Date.now() - start, loginCheck);
|
||||
}
|
||||
|
||||
@@ -874,6 +880,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
authorization,
|
||||
cloudAddress,
|
||||
hostname,
|
||||
serverId,
|
||||
},
|
||||
connectRPCObject,
|
||||
fork: undefined,
|
||||
|
||||
5
plugins/alexa/package-lock.json
generated
5
plugins/alexa/package-lock.json
generated
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"uuid": "^9.0.0"
|
||||
@@ -203,4 +202,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
|
||||
@@ -53,6 +53,12 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
title: "Pairing Key",
|
||||
description: "The pairing key used to validate requests from Alexa. Clear this key or delete the plugin to allow pairing with a different Alexa login.",
|
||||
},
|
||||
disableAutoAdd: {
|
||||
title: "Disable auto add",
|
||||
description: "Disable automatic enablement of devices.",
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
accessToken: Promise<string>;
|
||||
@@ -116,6 +122,10 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
if (!supportedTypes.has(device.type))
|
||||
return DeviceMixinStatus.NotSupported;
|
||||
|
||||
if (this.storageSettings.values.disableAutoAdd) {
|
||||
return DeviceMixinStatus.Skip;
|
||||
}
|
||||
|
||||
mixins.push(this.id);
|
||||
|
||||
const plugins = await systemManager.getComponent('plugins');
|
||||
@@ -671,7 +681,8 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
enum DeviceMixinStatus {
|
||||
NotSupported = 0,
|
||||
Setup = 1,
|
||||
AlreadySetup = 2
|
||||
AlreadySetup = 2,
|
||||
Skip = 3,
|
||||
}
|
||||
|
||||
class HttpResponseLoggingImpl implements AlexaHttpResponse {
|
||||
|
||||
4
plugins/amcrest/package-lock.json
generated
4
plugins/amcrest/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.162",
|
||||
"version": "0.0.164",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.162",
|
||||
"version": "0.0.164",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.162",
|
||||
"version": "0.0.164",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -199,6 +199,14 @@ export class AmcrestCameraClient {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async setWatermark(cameraNumber: number, enable: boolean) {
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[${cameraNumber - 1}].PictureTitle.EncodeBlend=${enable}`,
|
||||
responseType: 'text',
|
||||
});
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async listenEvents(): Promise<Destroyable> {
|
||||
const events = new EventEmitter();
|
||||
const url = `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[All]`;
|
||||
|
||||
@@ -9,12 +9,14 @@ export const amcrestAutoConfigureSettings: Setting = {
|
||||
};
|
||||
|
||||
export async function autoconfigureSettings(client: AmcrestCameraClient, cameraNumber: number) {
|
||||
await client.setWatermark(cameraNumber, false).catch(() => { });
|
||||
|
||||
const audioOptions: AudioStreamConfiguration = {
|
||||
codec: 'aac',
|
||||
sampleRate: 8000,
|
||||
};
|
||||
|
||||
await client.resetMotionDetection(cameraNumber);
|
||||
await client.resetMotionDetection(cameraNumber).catch(() => {});
|
||||
|
||||
return ac(
|
||||
() => client.getCodecs(cameraNumber),
|
||||
|
||||
4
plugins/cloud/package-lock.json
generated
4
plugins/cloud/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.2.37",
|
||||
"version": "0.2.47",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.2.37",
|
||||
"version": "0.2.47",
|
||||
"dependencies": {
|
||||
"@eneris/push-receiver": "^4.2.0",
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -53,5 +53,5 @@
|
||||
"@types/node": "^22.5.2",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"version": "0.2.37"
|
||||
"version": "0.2.47"
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const { deviceManager, endpointManager, systemManager } = sdk;
|
||||
|
||||
export const DEFAULT_SENDER_ID = '827888101440';
|
||||
const SCRYPTED_SERVER = localStorage.getItem('scrypted-server') || 'home.scrypted.app';
|
||||
const SCRYPTED_SERVER_PORT = 4001;
|
||||
|
||||
const SCRYPTED_CLOUD_MESSAGE_PATH = '/_punch/cloudmessage';
|
||||
|
||||
@@ -147,7 +148,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
type: 'boolean',
|
||||
description: 'Optional: Create a Cloudflare Tunnel to this server at a random domain name. Providing a Cloudflare token will allow usage of a custom domain name.',
|
||||
defaultValue: true,
|
||||
onPut: () => deviceManager.requestRestart(),
|
||||
},
|
||||
cloudflaredTunnelToken: {
|
||||
group: 'Cloudflare',
|
||||
@@ -258,6 +258,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.converters = [
|
||||
[ScryptedMimeTypes.LocalUrl, ScryptedMimeTypes.Url],
|
||||
[ScryptedMimeTypes.PushEndpoint, ScryptedMimeTypes.Url],
|
||||
['*/*', ScryptedMimeTypes.ServerId],
|
||||
];
|
||||
// legacy cleanup
|
||||
this.fromMimeType = undefined;
|
||||
@@ -266,10 +267,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
devices: [],
|
||||
});
|
||||
|
||||
this.storageSettings.settings.register.onPut = async () => {
|
||||
await this.sendRegistrationId(await this.manager.registrationId);
|
||||
}
|
||||
|
||||
this.storageSettings.settings.upnpStatus.onGet = async () => {
|
||||
return {
|
||||
hide: this.storageSettings.values.forwardingMode !== 'UPNP',
|
||||
@@ -313,11 +310,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
};
|
||||
|
||||
|
||||
this.storageSettings.settings.securePort.onPut = (ov, nv) => {
|
||||
if (ov && ov !== nv)
|
||||
this.log.a('Reload the Scrypted Cloud Plugin to apply the port change.');
|
||||
};
|
||||
|
||||
if (!this.storageSettings.values.certificate)
|
||||
this.storageSettings.values.certificate = createSelfSignedCertificate();
|
||||
|
||||
@@ -340,6 +332,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.refreshPortForward();
|
||||
}
|
||||
|
||||
// auto login from electron
|
||||
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 => {
|
||||
@@ -366,6 +359,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async getCachedRegistrationId() {
|
||||
return this.manager.currentRegistrationId || this.storageSettings.values.lastPersistedRegistrationId;
|
||||
}
|
||||
|
||||
async updatePortForward(upnpPort: number) {
|
||||
this.storageSettings.values.upnpPort = upnpPort;
|
||||
|
||||
@@ -431,8 +428,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
if (this.storageSettings.values.lastPersistedUpnpPort !== upnpPort || ip !== this.storageSettings.values.lastPersistedIp) {
|
||||
this.console.log('Registering IP and Port', ip, upnpPort);
|
||||
|
||||
const registrationId = await this.manager.registrationId;
|
||||
const data = await this.sendRegistrationId(registrationId);
|
||||
const data = await this.sendRegistrationId(await this.getCachedRegistrationId());
|
||||
if (data?.error)
|
||||
return;
|
||||
if (ip !== 'localhost' && ip !== data.ip_address && ip !== this.cloudflareTunnelHost) {
|
||||
@@ -451,11 +447,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
public: true,
|
||||
});
|
||||
const url = new URL(`https://${SCRYPTED_SERVER}/_punch/curl`);
|
||||
let { upnp_port, hostname } = this.getAuthority();
|
||||
let { port, hostname } = this.getAuthority();
|
||||
// scrypted cloud will replace localhost with requesting ip
|
||||
if (!hostname)
|
||||
hostname = 'localhost';
|
||||
url.searchParams.set('url', `https://${hostname}:${upnp_port}${pluginPath}/testPortForward`);
|
||||
url.searchParams.set('url', `https://${hostname}:${port}${pluginPath}/testPortForward`);
|
||||
const response = await httpFetch({
|
||||
url: url.toString(),
|
||||
responseType: 'json',
|
||||
@@ -621,13 +617,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
if (!hostname) {
|
||||
return {
|
||||
upnp_port,
|
||||
port: upnp_port,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
upnp_port,
|
||||
port: upnp_port,
|
||||
hostname,
|
||||
}
|
||||
@@ -638,6 +632,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
const q = qsstringify({
|
||||
...authority,
|
||||
cloudflare_hostname: this.cloudflareTunnelHost,
|
||||
registration_id,
|
||||
server_id: this.storageSettings.values.serverId,
|
||||
server_name: this.storageSettings.values.serverName,
|
||||
@@ -677,7 +672,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
this.console.log('registered', response.body);
|
||||
this.storageSettings.values.lastPersistedRegistrationId = registration_id;
|
||||
this.storageSettings.values.lastPersistedUpnpPort = authority.upnp_port;
|
||||
this.storageSettings.values.lastPersistedUpnpPort = authority.port;
|
||||
return response.body;
|
||||
}
|
||||
catch (e) {
|
||||
@@ -729,10 +724,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
|
||||
async convertMedia(data: string | Buffer | any, fromMimeType: string, toMimeType: string, options?: MediaObjectOptions): Promise<MediaObject | Buffer | any> {
|
||||
if (!toMimeType.startsWith(ScryptedMimeTypes.Url))
|
||||
throw new Error('unsupported cloud url conversion');
|
||||
|
||||
if (fromMimeType.startsWith(ScryptedMimeTypes.LocalUrl)) {
|
||||
if (toMimeType.startsWith(ScryptedMimeTypes.Url) && fromMimeType.startsWith(ScryptedMimeTypes.LocalUrl)) {
|
||||
// if cloudflare is enabled and the plugin isn't set up as a custom domain, try to use the cloudflare url for
|
||||
// short lived urls.
|
||||
if (this.cloudflareTunnel && this.storageSettings.values.forwardingMode !== 'Custom Domain') {
|
||||
@@ -746,7 +738,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
return this.whitelist(data.toString(), 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}`);
|
||||
}
|
||||
else if (fromMimeType.startsWith(ScryptedMimeTypes.PushEndpoint)) {
|
||||
else if (toMimeType.startsWith(ScryptedMimeTypes.Url) && fromMimeType.startsWith(ScryptedMimeTypes.PushEndpoint)) {
|
||||
const validDomain = this.getSSLHostname();
|
||||
if (validDomain)
|
||||
return Buffer.from(`https://${validDomain}${await this.getCloudMessagePath()}/${data}`);
|
||||
@@ -754,6 +746,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const url = `http://127.0.0.1/push/${data}`;
|
||||
return this.whitelist(url, 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}${SCRYPTED_CLOUD_MESSAGE_PATH}`);
|
||||
}
|
||||
else if (toMimeType === ScryptedMimeTypes.ServerId) {
|
||||
return this.storageSettings.values.serverId;
|
||||
}
|
||||
|
||||
throw new Error('unsupported cloud url conversion');
|
||||
}
|
||||
@@ -792,7 +787,8 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const args = qsstringify({
|
||||
...authority,
|
||||
|
||||
registration_id: await this.manager.registrationId,
|
||||
registration_id: await this.getCachedRegistrationId() || 'undefined',
|
||||
cloudflare_hostname: this.cloudflareTunnelHost,
|
||||
registration_secret: this.storageSettings.values.registrationSecret,
|
||||
server_id: this.storageSettings.values.serverId,
|
||||
server_name: this.storageSettings.values.serverName,
|
||||
@@ -833,7 +829,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const query = qsparse(url.searchParams);
|
||||
if (!query.callback_url && query.token_info && query.user_info) {
|
||||
this.storageSettings.values.token_info = query.token_info;
|
||||
this.storageSettings.values.lastPersistedRegistrationId = await this.manager.registrationId;
|
||||
this.storageSettings.values.lastPersistedRegistrationId = await this.getCachedRegistrationId();
|
||||
res.setHeader('Location', `https://${this.getHostname()}/endpoint/@scrypted/core/public/`);
|
||||
res.writeHead(302);
|
||||
res.end();
|
||||
@@ -844,6 +840,17 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (url.pathname === '/_punch/callback') {
|
||||
const query = qsparse(url.searchParams);
|
||||
if (query.registration_secret === this.storageSettings.values.registrationSecret) {
|
||||
res.writeHead(200);
|
||||
this.serverCallback(port, SCRYPTED_SERVER_PORT, SCRYPTED_SERVER);
|
||||
}
|
||||
else {
|
||||
res.writeHead(401);
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
else if (url.pathname === '/web/') {
|
||||
const validDomain = this.getSSLHostname();
|
||||
if (validDomain) {
|
||||
@@ -890,6 +897,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const port = (this.server.address() as any).port;
|
||||
this.console.log('scrypted cloud server listening on', port);
|
||||
|
||||
this.storageSettings.settings.cloudflareEnabled.onPut = () => {
|
||||
this.cloudflared?.child.kill();
|
||||
this.startCloudflared(port);
|
||||
};
|
||||
|
||||
const agent = new http.Agent({ maxSockets: Number.MAX_VALUE, keepAlive: true });
|
||||
this.proxy = HttpProxy.createProxy({
|
||||
agent,
|
||||
@@ -900,11 +912,12 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.proxy.on('proxyRes', (res, req) => {
|
||||
res.headers['X-Scrypted-Cloud'] = req.headers['x-scrypted-cloud'];
|
||||
res.headers['X-Scrypted-Direct-Address'] = req.headers['x-scrypted-direct-address'];
|
||||
res.headers['X-Scrypted-Server-Id'] = this.storageSettings.values.serverId;
|
||||
let domain = this.cloudflareTunnel;
|
||||
if (!domain && this.storageSettings.values.forwardingMode === 'Custom Domain' && this.storageSettings.values.hostname)
|
||||
domain = `https://${this.storageSettings.values.hostname}`;
|
||||
res.headers['X-Scrypted-Cloud-Address'] = domain;
|
||||
res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address, X-Scrypted-Cloud-Address';
|
||||
res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address, X-Scrypted-Cloud-Address, X-Scrypted-Server-Id';
|
||||
});
|
||||
|
||||
let backoff = 0;
|
||||
@@ -927,42 +940,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
if (Date.now() < backoff + 5000)
|
||||
return;
|
||||
backoff = Date.now();
|
||||
const random = Math.random().toString(36).substring(2);
|
||||
this.console.log('scrypted server requested a connection:', random);
|
||||
|
||||
const registrationId = await this.manager.registrationId;
|
||||
|
||||
const { address } = message;
|
||||
const [serverHost, serverPort] = address?.split(':') || [SCRYPTED_SERVER, 4001];
|
||||
|
||||
this.ensureReverseConnections(registrationId, serverPort, serverHost);
|
||||
|
||||
const client = tls.connect(serverPort, serverHost, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
client.on('close', () => this.console.log('scrypted server connection ended:', random));
|
||||
client.write(registrationId + '\n');
|
||||
const mux: any = new bpmux.BPMux(client as any);
|
||||
mux.on('handshake', async (socket: Duplex) => {
|
||||
this.ensureReverseConnections(registrationId, serverPort, serverHost);
|
||||
|
||||
this.console.warn('mux connection required');
|
||||
|
||||
let local: any;
|
||||
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
local = net.connect({
|
||||
port,
|
||||
host: '127.0.0.1',
|
||||
});
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
socket.pipe(local).pipe(socket);
|
||||
});
|
||||
mux.on('error', () => {
|
||||
client.destroy();
|
||||
});
|
||||
const [serverHost, serverPort] = address?.split(':') || [SCRYPTED_SERVER, SCRYPTED_SERVER_PORT];
|
||||
this.serverCallback(port, Number(serverPort), serverHost);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -989,6 +969,40 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
}
|
||||
|
||||
serverCallback(port: number, serverPort: number, serverHost: string) {
|
||||
const random = Math.random().toString(36).substring(2);
|
||||
this.console.log('scrypted server requested a connection:', random);
|
||||
|
||||
this.ensureReverseConnections(serverPort, serverHost);
|
||||
|
||||
const client = tls.connect(serverPort, serverHost, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
client.on('close', () => this.console.log('scrypted server connection ended:', random));
|
||||
client.write(this.serverIdentifier + '\n');
|
||||
const mux: any = new bpmux.BPMux(client as any);
|
||||
mux.on('handshake', async (socket: Duplex) => {
|
||||
this.ensureReverseConnections(serverPort, serverHost);
|
||||
|
||||
this.console.warn('mux connection required');
|
||||
|
||||
let local: any;
|
||||
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
local = net.connect({
|
||||
port,
|
||||
host: '127.0.0.1',
|
||||
});
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
socket.pipe(local).pipe(socket);
|
||||
});
|
||||
mux.on('error', () => {
|
||||
client.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
async startCloudflared(quickTunnelPort: number) {
|
||||
while (true) {
|
||||
try {
|
||||
@@ -1004,6 +1018,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
if (this.storageSettings.values.cloudflaredTunnelCredentials && this.storageSettings.values.cloudflaredTunnelCustomDomain) {
|
||||
const tunnelUrl = `http://127.0.0.1:${quickTunnelPort}`;
|
||||
const url = this.cloudflareTunnel = `https://${this.storageSettings.values.cloudflaredTunnelCustomDomain}`;
|
||||
this.updateExternalAddresses();
|
||||
this.console.log(`cloudflare url mapped ${this.cloudflareTunnel} to ${tunnelUrl}`);
|
||||
|
||||
const ret = await runLocallyManagedTunnel(this.storageSettings.values.cloudflaredTunnelCredentials, tunnelUrl, cloudflareD, bin);
|
||||
@@ -1038,7 +1053,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
const lines = string.split('\n');
|
||||
for (const line of lines) {
|
||||
if ((line.includes('Unregistered tunnel connection') || line.includes('Register tunnel error'))
|
||||
if ((line.includes('Unregistered tunnel connection')
|
||||
|| line.includes('Connection terminated error')
|
||||
|| line.includes('Register tunnel error')
|
||||
|| line.includes('Failed to get tunnel'))
|
||||
&& deferred.finished) {
|
||||
this.console.warn('Cloudflare registration failed after tunnel started. The old tunnel may be invalid. Terminating.');
|
||||
cloudflareTunnel.child.kill();
|
||||
@@ -1113,13 +1131,18 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
}
|
||||
|
||||
ensureReverseConnections(registrationId: string, serverPort: number, serverHost: string) {
|
||||
get serverIdentifier() {
|
||||
const serverIdentifier = `${this.storageSettings.values.registrationSecret}@${this.storageSettings.values.serverId}`;
|
||||
return serverIdentifier;
|
||||
}
|
||||
|
||||
ensureReverseConnections(serverPort: number, serverHost: string) {
|
||||
while (this.reverseConnections.size < 10) {
|
||||
this.createReverseConnection(registrationId, serverPort, serverHost);
|
||||
this.createReverseConnection(serverPort, serverHost);
|
||||
}
|
||||
}
|
||||
|
||||
async createReverseConnection(registrationId: string, serverPort: number, serverHost: string) {
|
||||
async createReverseConnection(serverPort: number, serverHost: string) {
|
||||
const client = tls.connect(serverPort, serverHost, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
@@ -1131,9 +1154,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.reverseConnections.delete(client);
|
||||
|
||||
if (claimed)
|
||||
this.ensureReverseConnections(registrationId, serverPort, serverHost);
|
||||
this.ensureReverseConnections(serverPort, serverHost);
|
||||
});
|
||||
client.write(`reverse:${registrationId}\n`);
|
||||
|
||||
client.write(`reverse:${this.serverIdentifier}\n`);
|
||||
|
||||
try {
|
||||
const read = await readLine(client);
|
||||
|
||||
@@ -10,6 +10,7 @@ export declare interface PushManager {
|
||||
|
||||
export class PushManager extends EventEmitter {
|
||||
registrationId: Promise<string>;
|
||||
currentRegistrationId: string;
|
||||
|
||||
constructor(public senderId: string) {
|
||||
super();
|
||||
@@ -47,11 +48,12 @@ export class PushManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const stopListeningToCredentials = instance.onCredentialsChanged(({ oldCredentials, newCredentials }) => {
|
||||
this.currentRegistrationId = newCredentials.fcm.token;
|
||||
savedConfig.credentials = newCredentials;
|
||||
deferred.resolve(newCredentials.fcm.token);
|
||||
this.registrationId = Promise.resolve(newCredentials.fcm.token);
|
||||
deferred.resolve(this.currentRegistrationId);
|
||||
this.registrationId = Promise.resolve( this.currentRegistrationId);
|
||||
saveConfig();
|
||||
this.emit('registrationId', newCredentials.fcm.token);
|
||||
this.emit('registrationId', this.currentRegistrationId);
|
||||
});
|
||||
|
||||
const stopListeningToNotifications = instance.onNotification(({ message }) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export function qsstringify(dict: any) {
|
||||
const params = new URLSearchParams();
|
||||
for (const [k, v] of Object.entries(dict)) {
|
||||
if (v == null)
|
||||
continue;
|
||||
params.set(k, v?.toString());
|
||||
}
|
||||
|
||||
|
||||
167
plugins/core/package-lock.json
generated
167
plugins/core/package-lock.json
generated
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.68",
|
||||
"version": "0.3.86",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.68",
|
||||
"version": "0.3.86",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"mime": "^3.0.0",
|
||||
"mime": "^4.0.4",
|
||||
"node-pty": "^1.0.0",
|
||||
"router": "^1.3.8",
|
||||
"typescript": "^5.4.2"
|
||||
"typescript": "^5.6.2",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node": "^20.11.26"
|
||||
"@types/node": "^22.7.4"
|
||||
}
|
||||
},
|
||||
"../../../sdk": {
|
||||
@@ -88,23 +88,22 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.45",
|
||||
"version": "0.3.63",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"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"
|
||||
"rimraf": "^6.0.1",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
@@ -116,11 +115,11 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
@@ -131,19 +130,13 @@
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
|
||||
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz",
|
||||
"integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==",
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
@@ -160,14 +153,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
|
||||
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa"
|
||||
],
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
"mime": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
@@ -195,7 +191,7 @@
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "1.3.8",
|
||||
@@ -233,9 +229,9 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
|
||||
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -245,9 +241,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
@@ -257,6 +253,17 @@
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -274,40 +281,33 @@
|
||||
"@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": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"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"
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
|
||||
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "20.11.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz",
|
||||
"integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==",
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
@@ -321,9 +321,9 @@
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
|
||||
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.20.0",
|
||||
@@ -346,7 +346,7 @@
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"router": {
|
||||
"version": "1.3.8",
|
||||
@@ -383,20 +383,25 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
|
||||
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ=="
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.68",
|
||||
"version": "0.3.86",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
@@ -42,13 +42,13 @@
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"mime": "^3.0.0",
|
||||
"mime": "^4.0.4",
|
||||
"node-pty": "^1.0.0",
|
||||
"router": "^1.3.8",
|
||||
"typescript": "^5.4.2"
|
||||
"typescript": "^5.6.2",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node": "^20.11.26"
|
||||
"@types/node": "^22.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import sdk, { BufferConverter, HttpRequest, HttpRequestHandler, HttpResponse, HttpResponseOptions, MediaObject, RequestMediaObject, ScryptedDeviceBase, ScryptedMimeTypes } from "@scrypted/sdk";
|
||||
import crypto from 'crypto';
|
||||
import mime from "mime/lite";
|
||||
import path from 'path';
|
||||
|
||||
const { endpointManager } = sdk;
|
||||
@@ -47,18 +46,21 @@ export class BufferHost extends ScryptedDeviceBase implements HttpRequestHandler
|
||||
options.headers['Content-Disposition'] = 'attachment';
|
||||
}
|
||||
|
||||
response.send(file.data as Buffer, );
|
||||
response.send(file.data as Buffer,);
|
||||
}
|
||||
|
||||
async convert(buffer: string, fromMimeType: string, toMimeType: string): Promise<Buffer> {
|
||||
const uuid = uuidv4();
|
||||
|
||||
const { default: mime } = await import('mime');
|
||||
|
||||
const endpoint = await (this.secure ? endpointManager.getPublicLocalEndpoint(this.nativeId) : endpointManager.getInsecurePublicLocalEndpoint(this.nativeId));
|
||||
const extension = mime.getExtension(fromMimeType);
|
||||
|
||||
const filename = uuid + (extension ? `.${extension}` : '');
|
||||
|
||||
this.hosted.set(`/${filename}`, { data: buffer, fromMimeType, toMimeType });
|
||||
setTimeout(() => this.hosted.delete(`/${filename}`), 10 * 60 * 1000); // free this resource after 10 min.
|
||||
|
||||
return Buffer.from(`${endpoint}${filename}`);
|
||||
}
|
||||
@@ -131,6 +133,7 @@ export class RequestMediaObjectHost extends ScryptedDeviceBase implements HttpRe
|
||||
const endpoint = await (toMimeType === ScryptedMimeTypes.LocalUrl ? endpointManager.getPublicLocalEndpoint(this.nativeId) : endpointManager.getInsecurePublicLocalEndpoint(this.nativeId));
|
||||
const data = await request();
|
||||
fromMimeType = data.mimeType;
|
||||
const { default: mime } = await import('mime');
|
||||
const extension = mime.getExtension(fromMimeType);
|
||||
|
||||
const filename = uuid + (extension ? `.${extension}` : '');
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { readFileAsString, tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
|
||||
import sdk, { DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from '@scrypted/sdk';
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import { writeFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import Router from 'router';
|
||||
import yaml from 'yaml';
|
||||
import { getUsableNetworkAddresses } from '../../../server/src/ip';
|
||||
import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
|
||||
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
|
||||
@@ -48,7 +51,41 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
service.setLocalAddresses(this.localAddresses);
|
||||
},
|
||||
}
|
||||
},
|
||||
releaseChannel: {
|
||||
group: 'Advanced',
|
||||
title: 'Server Release Channel',
|
||||
description: 'The release channel to use for server updates. A specific version or tag can be manually entered as well. Changing this setting will update the image field in /root/.scrypted/docker-compose.yml. Invalid values may prevent the server from properly starting.',
|
||||
defaultValue: 'Default',
|
||||
choices: [
|
||||
'Default',
|
||||
'latest',
|
||||
'beta',
|
||||
`v${sdk.serverVersion}-jammy-full`,
|
||||
],
|
||||
combobox: true,
|
||||
onPut: (ov, nv) => {
|
||||
this.updateReleaseChannel(nv);
|
||||
},
|
||||
mapGet: () => {
|
||||
try {
|
||||
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
|
||||
// @ts-ignore
|
||||
const image: string = dockerCompose.contents.get('services').get('scrypted').get('image');
|
||||
const label = image.split(':')[1] || undefined;
|
||||
return label || 'Default';
|
||||
}
|
||||
catch (e) {
|
||||
return 'Default';
|
||||
}
|
||||
}
|
||||
},
|
||||
pullImage: {
|
||||
hide: true,
|
||||
onPut: () => {
|
||||
this.setPullImage();
|
||||
}
|
||||
},
|
||||
});
|
||||
indexHtml: string;
|
||||
|
||||
@@ -61,6 +98,8 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
|
||||
|
||||
checkLxcDependencies();
|
||||
|
||||
this.storageSettings.settings.releaseChannel.hide = process.env.SCRYPTED_INSTALL_ENVIRONMENT !== 'lxc-docker';
|
||||
|
||||
this.indexHtml = readFileAsString('dist/index.html');
|
||||
|
||||
(async () => {
|
||||
@@ -248,6 +287,27 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setPullImage() {
|
||||
writeFileSync(path.join(process.env.SCRYPTED_VOLUME, '.pull'), '');
|
||||
}
|
||||
|
||||
async updateReleaseChannel(releaseChannel: string) {
|
||||
if (!releaseChannel || releaseChannel === 'Default')
|
||||
releaseChannel = '';
|
||||
else
|
||||
releaseChannel = `:${releaseChannel}`;
|
||||
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
|
||||
// @ts-ignore
|
||||
dockerCompose.contents.get('services').get('scrypted').set('image', `ghcr.io/koush/scrypted${releaseChannel}`);
|
||||
yaml.stringify(dockerCompose);
|
||||
writeFileSync('/root/.scrypted/docker-compose.yml', yaml.stringify(dockerCompose));
|
||||
this.setPullImage();
|
||||
|
||||
const serviceControl = await sdk.systemManager.getComponent("service-control");
|
||||
await serviceControl.exit().catch(() => { });
|
||||
await serviceControl.restart();
|
||||
}
|
||||
}
|
||||
|
||||
export default ScryptedCore;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import child_process from 'child_process';
|
||||
import { once } from 'events';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import { stdout } from 'process';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
|
||||
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC = 'lxc';
|
||||
|
||||
@@ -42,12 +42,53 @@ export async function checkLxcDependencies() {
|
||||
sdk.log.a('Failed to daemon-reload systemd.');
|
||||
}
|
||||
|
||||
try {
|
||||
const output = await new Promise<string>((r, f) => child_process.exec("sh -c 'apt list --installed | grep level-zero/'", (err, stdout, stderr) => {
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
}));
|
||||
|
||||
const cpuModel = os.cpus()[0].model;
|
||||
if (cpuModel.includes('Core') && cpuModel.includes('Ultra')) {
|
||||
if (
|
||||
// apt
|
||||
!output.includes('level-zero/')
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install intel-driver-compiler-npu.');
|
||||
else
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// level-zero crashes openvino on older CPU due to illegal instruction.
|
||||
// so ensure it is not installed if this is not a core ultra system with npu.
|
||||
if (
|
||||
// apt
|
||||
output.includes('level-zero/')
|
||||
) {
|
||||
const cp = child_process.spawn('apt', ['-y', 'remove', 'level-zero']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
console.log('level-zero removed', exitCode);
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
sdk.log.a('Failed to verify/install intel-driver-compiler-npu.');
|
||||
}
|
||||
|
||||
try {
|
||||
// intel opencl icd is broken from their official apt repos on kernel versions 6.8, which ships with ubuntu 24.04 and proxmox 8.2.
|
||||
// the intel apt repo has not been updated yet.
|
||||
// the current workaround is to install the release manually.
|
||||
// https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
|
||||
const output = await new Promise<string>((r, f) => child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
@@ -59,8 +100,10 @@ export async function checkLxcDependencies() {
|
||||
output.includes('Version: 23')
|
||||
// was installed via script at some point
|
||||
|| output.includes('Version: 24.13.29138.7')
|
||||
// current script version: 24.17.29377.6
|
||||
) {
|
||||
|| output.includes('Version: 24.26.30049.6')
|
||||
|| output.includes('Version: 24.31.30508.7')
|
||||
// current script version: 24.35.30872.22
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
@@ -73,30 +116,6 @@ export async function checkLxcDependencies() {
|
||||
sdk.log.a('Failed to verify/install intel-opencl-icd version.');
|
||||
}
|
||||
|
||||
try {
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-driver-compiler-npu'", (err, stdout, stderr) => {
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
}));
|
||||
|
||||
if (
|
||||
// apt
|
||||
output.includes('No packages found')
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install intel-driver-compiler-npu.');
|
||||
else
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
sdk.log.a('Failed to verify/install intel-driver-compiler-npu.');
|
||||
}
|
||||
|
||||
if (needRestart)
|
||||
sdk.log.a('A system update is pending. Please restart Scrypted to apply changes.');
|
||||
}
|
||||
|
||||
@@ -17,9 +17,13 @@ export class PluginSocketService extends ScryptedDeviceBase implements StreamSer
|
||||
throw new Error('must provide pluginId');
|
||||
|
||||
const plugins = await sdk.systemManager.getComponent('plugins');
|
||||
const replPort: number = await plugins.getRemoteServicePort(pluginId, this.serviceName);
|
||||
const servicePort = await plugins.getRemoteServicePort(pluginId, this.serviceName) as number | [number, string];
|
||||
const [port, host] = Array.isArray(servicePort) ? servicePort : [servicePort, undefined];
|
||||
|
||||
const socket = net.connect(replPort);
|
||||
const socket = net.connect({
|
||||
port,
|
||||
host,
|
||||
});
|
||||
await once(socket, 'connect');
|
||||
|
||||
const queue = createAsyncQueue<Buffer>();
|
||||
|
||||
2
plugins/coreml/.vscode/settings.json
vendored
2
plugins/coreml/.vscode/settings.json
vendored
@@ -1,6 +1,6 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
|
||||
68
plugins/coreml/package-lock.json
generated
68
plugins/coreml/package-lock.json
generated
@@ -1,36 +1,35 @@
|
||||
{
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.70",
|
||||
"version": "0.1.76",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.70",
|
||||
"version": "0.1.76",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.31",
|
||||
"version": "0.3.77",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.2.1",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"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"
|
||||
"rimraf": "^6.0.1",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
@@ -42,11 +41,11 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.10"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
@@ -61,25 +60,24 @@
|
||||
"@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": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.2.1",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"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"
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.10",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,18 @@
|
||||
"interfaces": [
|
||||
"Settings",
|
||||
"DeviceProvider",
|
||||
"ClusterForkInterface",
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview"
|
||||
]
|
||||
],
|
||||
"labels": {
|
||||
"require": [
|
||||
"@scrypted/coreml"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.70"
|
||||
"version": "0.1.76"
|
||||
}
|
||||
|
||||
@@ -69,9 +69,13 @@ def parse_labels(userDefined):
|
||||
return parse_label_contents(classes)
|
||||
|
||||
|
||||
class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProvider):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
class CoreMLPlugin(
|
||||
PredictPlugin,
|
||||
scrypted_sdk.Settings,
|
||||
scrypted_sdk.DeviceProvider,
|
||||
):
|
||||
def __init__(self, nativeId: str | None = None, forked: bool = False):
|
||||
super().__init__(nativeId=nativeId, forked=forked)
|
||||
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
if model == "Default" or model not in availableModels:
|
||||
@@ -139,7 +143,9 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
if not self.forked:
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
@@ -148,6 +154,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "CoreML Face Recognition",
|
||||
@@ -160,6 +167,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "CoreML Text Recognition",
|
||||
@@ -176,10 +184,10 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
|
||||
async def getDevice(self, nativeId: str) -> Any:
|
||||
if nativeId == "facerecognition":
|
||||
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(nativeId)
|
||||
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(self, nativeId)
|
||||
return self.faceDevice
|
||||
if nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or CoreMLTextRecognition(nativeId)
|
||||
self.textDevice = self.textDevice or CoreMLTextRecognition(self, nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
|
||||
@@ -227,7 +235,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
return ret
|
||||
|
||||
if self.scrypted_yolo_nas:
|
||||
predictions = list(out_dict.values())
|
||||
predictions = list(out_dict.values())
|
||||
objs = yolo.parse_yolo_nas(predictions)
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
@@ -250,13 +258,13 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
|
||||
for r in objects:
|
||||
obj = Prediction(
|
||||
r["classId"].astype(float),
|
||||
r["confidence"].astype(float),
|
||||
r["classId"],
|
||||
r["confidence"],
|
||||
Rectangle(
|
||||
r["xmin"].astype(float),
|
||||
r["ymin"].astype(float),
|
||||
r["xmax"].astype(float),
|
||||
r["ymax"].astype(float),
|
||||
r["xmin"],
|
||||
r["ymin"],
|
||||
r["xmax"],
|
||||
r["ymax"],
|
||||
),
|
||||
)
|
||||
objs.append(obj)
|
||||
@@ -275,9 +283,9 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
),
|
||||
)
|
||||
|
||||
coordinatesList = out_dict["coordinates"].astype(float)
|
||||
coordinatesList = out_dict["coordinates"]
|
||||
|
||||
for index, confidenceList in enumerate(out_dict["confidence"].astype(float)):
|
||||
for index, confidenceList in enumerate(out_dict["confidence"]):
|
||||
values = confidenceList
|
||||
maxConfidenceIndex = max(range(len(values)), key=values.__getitem__)
|
||||
maxConfidence = confidenceList[maxConfidenceIndex]
|
||||
|
||||
@@ -29,8 +29,8 @@ def cosine_similarity(vector_a, vector_b):
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(8, "Vision-Predict")
|
||||
|
||||
class CoreMLFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin, nativeId)
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-face")
|
||||
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-face")
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ from predict.text_recognize import TextRecognition
|
||||
|
||||
|
||||
class CoreMLTextRecognition(TextRecognition):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin, nativeId)
|
||||
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-text")
|
||||
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-text")
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from coreml import CoreMLPlugin
|
||||
import predict
|
||||
|
||||
def create_scrypted_plugin():
|
||||
return CoreMLPlugin()
|
||||
|
||||
async def fork():
|
||||
return predict.Fork(CoreMLPlugin)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
|
||||
numpy==1.26.4
|
||||
coremltools==7.2
|
||||
coremltools==8.0
|
||||
Pillow==10.3.0
|
||||
opencv-python==4.10.0.84
|
||||
opencv-python-headless==4.10.0.84
|
||||
|
||||
5
plugins/diagnostics/.gitignore
vendored
Normal file
5
plugins/diagnostics/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/
|
||||
.venv
|
||||
9
plugins/diagnostics/.npmignore
Normal file
9
plugins/diagnostics/.npmignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
*.map
|
||||
fs
|
||||
src
|
||||
.vscode
|
||||
dist/*.js
|
||||
.venv
|
||||
23
plugins/diagnostics/.vscode/launch.json
vendored
Normal file
23
plugins/diagnostics/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"address": "${config:scrypted.debugHost}",
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"**/plugin-remote-worker.*",
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"sourceMaps": true,
|
||||
"localRoot": "${workspaceFolder}/out",
|
||||
"remoteRoot": "/plugin/",
|
||||
"type": "node"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugins/diagnostics/.vscode/settings.json
vendored
Normal file
4
plugins/diagnostics/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "koushik-winvm",
|
||||
}
|
||||
20
plugins/diagnostics/.vscode/tasks.json
vendored
Normal file
20
plugins/diagnostics/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "scrypted: deploy+debug",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "silent",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
|
||||
},
|
||||
]
|
||||
}
|
||||
3
plugins/diagnostics/README.md
Normal file
3
plugins/diagnostics/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Scrypted Diagnostics Plugin
|
||||
|
||||
This plugin can be used to run diagnostics on the system and supported devices. The results from the diagnostics can be seen in the `Log`.
|
||||
848
plugins/diagnostics/package-lock.json
generated
Normal file
848
plugins/diagnostics/package-lock.json
generated
Normal file
@@ -0,0 +1,848 @@
|
||||
{
|
||||
"name": "@scrypted/diagnostics",
|
||||
"version": "0.0.19",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/diagnostics",
|
||||
"version": "0.0.19",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.69",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.2.1",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"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": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
||||
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
|
||||
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
||||
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
|
||||
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
|
||||
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
|
||||
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.5",
|
||||
"@img/sharp-darwin-x64": "0.33.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||
"@img/sharp-linux-arm": "0.33.5",
|
||||
"@img/sharp-linux-arm64": "0.33.5",
|
||||
"@img/sharp-linux-s390x": "0.33.5",
|
||||
"@img/sharp-linux-x64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||
"@img/sharp-wasm32": "0.33.5",
|
||||
"@img/sharp-win32-ia32": "0.33.5",
|
||||
"@img/sharp-win32-x64": "0.33.5"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
||||
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
|
||||
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-linux-arm": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
||||
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-s390x": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
|
||||
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-wasm32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
|
||||
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@emnapi/runtime": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-win32-ia32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
|
||||
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-win32-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
||||
"optional": true
|
||||
},
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@types/node": "^20.11.0",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.2.1",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.10",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||
},
|
||||
"sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||
"requires": {
|
||||
"@img/sharp-darwin-arm64": "0.33.5",
|
||||
"@img/sharp-darwin-x64": "0.33.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||
"@img/sharp-linux-arm": "0.33.5",
|
||||
"@img/sharp-linux-arm64": "0.33.5",
|
||||
"@img/sharp-linux-s390x": "0.33.5",
|
||||
"@img/sharp-linux-x64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||
"@img/sharp-wasm32": "0.33.5",
|
||||
"@img/sharp-win32-ia32": "0.33.5",
|
||||
"@img/sharp-win32-x64": "0.33.5",
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.3"
|
||||
}
|
||||
},
|
||||
"simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"requires": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"optional": true
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
37
plugins/diagnostics/package.json
Normal file
37
plugins/diagnostics/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@scrypted/diagnostics",
|
||||
"version": "0.0.19",
|
||||
"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"
|
||||
},
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"diagnostics"
|
||||
],
|
||||
"scrypted": {
|
||||
"name": "Diagnostics",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"Settings"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
}
|
||||
}
|
||||
565
plugins/diagnostics/src/main.ts
Normal file
565
plugins/diagnostics/src/main.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
import { Deferred } from '@scrypted/common/src/deferred';
|
||||
import { safeKillFFmpeg } from '@scrypted/common/src/media-helpers';
|
||||
import sdk, { Camera, FFmpegInput, Image, MediaObject, MediaStreamDestination, MotionSensor, Notifier, ObjectDetection, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, VideoCamera } from '@scrypted/sdk';
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import child_process from 'child_process';
|
||||
import { once } from 'events';
|
||||
import fs from 'fs';
|
||||
import net from 'net';
|
||||
import os from 'os';
|
||||
import sharp from 'sharp';
|
||||
import { httpFetch } from '../../../server/src/fetch/http-fetch';
|
||||
class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
testDevice: {
|
||||
group: 'Device',
|
||||
title: 'Validation Device',
|
||||
description: "Select a device to validate.",
|
||||
type: 'device',
|
||||
deviceFilter: `type === '${ScryptedDeviceType.Camera}' || type === '${ScryptedDeviceType.Doorbell}' || type === '${ScryptedDeviceType.Notifier}'`,
|
||||
immediate: true,
|
||||
},
|
||||
validateDevice: {
|
||||
console: true,
|
||||
group: 'Device',
|
||||
title: 'Validate Device',
|
||||
description: 'Validate the device configuration.',
|
||||
type: 'button',
|
||||
onPut: async () => {
|
||||
this.validateDevice();
|
||||
},
|
||||
},
|
||||
validateSystem: {
|
||||
console: true,
|
||||
group: 'System',
|
||||
title: 'Validate System',
|
||||
description: 'Validate the system configuration.',
|
||||
type: 'button',
|
||||
onPut: () => this.validateSystem(),
|
||||
},
|
||||
});
|
||||
|
||||
loggedMotion = new Map<string, number>();
|
||||
loggedButton = new Map<string, number>();
|
||||
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
this.on = this.on || false;
|
||||
|
||||
sdk.systemManager.listen((eventSource, eventDetails, eventData) => {
|
||||
if (!eventData || !eventSource?.id)
|
||||
return;
|
||||
|
||||
if (eventDetails.eventInterface === ScryptedInterface.MotionSensor) {
|
||||
this.loggedMotion.set(eventSource.id, Date.now());
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventDetails.eventInterface === ScryptedInterface.BinarySensor) {
|
||||
this.loggedButton.set(eventSource.id, Date.now());
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: any) {
|
||||
await this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
warnStep(console: Console, result: string) {
|
||||
console.log(''.padEnd(24), `\x1b[33m${result}\x1b[0m`);
|
||||
}
|
||||
|
||||
async validate(console: Console, stepName: string, step: Promise<any> | (() => Promise<any>)) {
|
||||
try {
|
||||
if (step instanceof Function)
|
||||
step = step();
|
||||
console.log(stepName.padEnd(24), '\x1b[34mRunning\x1b[0m');
|
||||
const result = await step;
|
||||
console.log(''.padEnd(24), `\x1b[32m${result || 'OK'}\x1b[0m`);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(stepName.padEnd(24), '\x1b[31m Failed\x1b[0m'.padEnd(24), (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async validateDevice() {
|
||||
const device = this.storageSettings.values.testDevice as ScryptedDevice & any;
|
||||
const console = sdk.deviceManager.getMixinConsole(device.id);
|
||||
|
||||
console.log(''.padEnd(44, '='));
|
||||
console.log(`Device Validation: ${device?.name}`);
|
||||
console.log(''.padEnd(44, '='));
|
||||
|
||||
await this.validate(console, 'Device Selected', async () => {
|
||||
if (!device)
|
||||
throw new Error('Select a device in the Settings UI.');
|
||||
});
|
||||
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
if (device.type === ScryptedDeviceType.Camera || device.type === ScryptedDeviceType.Doorbell) {
|
||||
await this.validateCamera(device);
|
||||
}
|
||||
else if (device.type === ScryptedDeviceType.Notifier) {
|
||||
await this.validateNotifier(device);
|
||||
}
|
||||
|
||||
console.log(''.padEnd(44, '='));
|
||||
console.log(`Device Validation Complete: ${device?.name}`);
|
||||
console.log(''.padEnd(44, '='));
|
||||
}
|
||||
|
||||
async validateNotifier(device: ScryptedDevice & Notifier) {
|
||||
const console = sdk.deviceManager.getMixinConsole(device.id);
|
||||
|
||||
await this.validate(console, 'Test Notification', async () => {
|
||||
const logo = await httpFetch({
|
||||
url: 'https://home.scrypted.app/_punch/web_hi_res_512.png',
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
const mo = await sdk.mediaManager.createMediaObject(logo.body, 'image/png');
|
||||
await device.sendNotification('Scrypted Diagnostics', {
|
||||
body: 'Body',
|
||||
subtitle: 'Subtitle',
|
||||
android: {
|
||||
channel: 'diagnostics',
|
||||
}
|
||||
}, mo);
|
||||
|
||||
this.warnStep(console, 'Check the device for the notification.');
|
||||
});
|
||||
}
|
||||
|
||||
async validateCamera(device: ScryptedDevice & Camera & VideoCamera & MotionSensor) {
|
||||
const console = sdk.deviceManager.getMixinConsole(device.id);
|
||||
|
||||
await this.validate(console, 'Device Capabilities', async () => {
|
||||
if (!device.interfaces.includes(ScryptedInterface.MotionSensor))
|
||||
throw new Error('Motion Sensor not found.');
|
||||
|
||||
if (device.type === ScryptedDeviceType.Doorbell && !device.interfaces.includes(ScryptedInterface.BinarySensor))
|
||||
throw new Error('Doorbell button not found.');
|
||||
});
|
||||
|
||||
await this.validate(console, 'Motion Detection', async () => {
|
||||
if (!device.interfaces.includes(ScryptedInterface.MotionSensor))
|
||||
throw new Error('Motion Sensor not found. Enabling a software motion sensor extension is recommended.');
|
||||
|
||||
if (device.providedInterfaces.includes(ScryptedInterface.MotionSensor)) {
|
||||
if (device.interfaces.find(i => i.startsWith('ObjectDetection:true')))
|
||||
this.warnStep(console, 'Camera hardware provides motion events, but a software motion detector is enabled. Consider disabling the software motion detector.');
|
||||
}
|
||||
});
|
||||
|
||||
if (device.interfaces.includes(ScryptedInterface.MotionSensor)) {
|
||||
await this.validate(console, 'Recent Motion', async () => {
|
||||
|
||||
const lastMotion = this.loggedMotion.get(device.id);
|
||||
if (!lastMotion)
|
||||
throw new Error('No recent motion detected. Go wave your hand in front of the camera.');
|
||||
if (Date.now() - lastMotion > 8 * 60 * 60 * 1000)
|
||||
throw new Error('Last motion was over 8 hours ago.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (device.type === ScryptedDeviceType.Doorbell) {
|
||||
await this.validate(console, 'Recent Button Press', async () => {
|
||||
const lastButton = this.loggedButton.get(device.id);
|
||||
if (!lastButton)
|
||||
throw new Error('No recent button press detected. Go press the doorbell button.');
|
||||
if (Date.now() - lastButton > 8 * 60 * 60 * 1000)
|
||||
throw new Error('Last button press was over 8 hours ago.');
|
||||
});
|
||||
}
|
||||
|
||||
const validateMedia = async (stepName: string, mo: Promise<MediaObject>, snapshot = false, and?: () => Promise<void>) => {
|
||||
await this.validate(console, stepName, async () => {
|
||||
if (snapshot && !device.interfaces.includes(ScryptedInterface.Camera))
|
||||
throw new Error('Snapshot not supported. Enable the Snapshot extension.');
|
||||
const jpeg = await sdk.mediaManager.convertMediaObjectToBuffer(await mo, 'image/jpeg');
|
||||
const metadata = await sharp(jpeg).metadata();
|
||||
if (!metadata.width || !metadata.height || metadata.width < 100 || metadata.height < 100)
|
||||
throw new Error('Malformed image.');
|
||||
if (!and && snapshot && device.pluginId === '@scrypted/unifi-protect' && metadata.width < 1280)
|
||||
this.warnStep(console, 'Unifi Protect provides low quality snapshots. Consider using Snapshot from Prebuffer for full resolution screenshots.');
|
||||
await and?.();
|
||||
});
|
||||
};
|
||||
|
||||
await validateMedia('Snapshot', device.takePicture({
|
||||
reason: 'event',
|
||||
}), true);
|
||||
|
||||
await this.validate(console, 'Streams', async () => {
|
||||
const vsos = await device.getVideoStreamOptions();
|
||||
|
||||
if (!vsos?.length)
|
||||
throw new Error('Stream configuration invalid.');
|
||||
|
||||
if (vsos.length < 3)
|
||||
this.warnStep(console, `Camera has ${vsos.length} substream. Three streams are recommended.`);
|
||||
|
||||
const cloudStreams = vsos.filter(vso => vso.source === 'cloud');
|
||||
if (cloudStreams.length)
|
||||
this.warnStep(console, `Cloud camera. Upgrade recommended.`);
|
||||
|
||||
const usedStreams = vsos.filter(vso => vso.destinations?.length);
|
||||
if (usedStreams.length < Math.min(3, vsos.length))
|
||||
this.warnStep(console, `Unused streams detected.`);
|
||||
});
|
||||
|
||||
const getVideoStream = async (destination: MediaStreamDestination) => {
|
||||
if (!device.interfaces.includes(ScryptedInterface.VideoCamera))
|
||||
throw new Error('Streaming not supported.');
|
||||
return await device.getVideoStream({
|
||||
destination,
|
||||
prebuffer: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const validated = new Set<string | undefined>();
|
||||
const validateMediaStream = async (stepName: string, destination: MediaStreamDestination) => {
|
||||
const vsos = await device.getVideoStreamOptions();
|
||||
const streamId = vsos.find(vso => vso.destinations?.includes(destination))?.id;
|
||||
|
||||
if (validated.has(streamId)) {
|
||||
await this.validate(console, stepName, async () => "Skipped (Duplicate)");
|
||||
return;
|
||||
}
|
||||
|
||||
validated.add(streamId);
|
||||
|
||||
const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(await getVideoStream(destination), ScryptedMimeTypes.FFmpegInput);
|
||||
if (ffmpegInput.mediaStreamOptions?.video?.codec !== 'h264')
|
||||
this.warnStep(console, `Stream ${stepName} is using codec ${ffmpegInput.mediaStreamOptions?.video?.codec}. h264 is recommended.`);
|
||||
|
||||
await validateMedia(stepName, getVideoStream(destination));
|
||||
const start = Date.now();
|
||||
await validateMedia(stepName + ' (IDR)', getVideoStream(destination), false, async () => {
|
||||
const end = Date.now();
|
||||
if (end - start > 5000)
|
||||
throw new Error(`High IDR Interval. This may cause issues with HomeKit Secure Video. Adjust codec configuration if possible.`);
|
||||
});
|
||||
};
|
||||
|
||||
await validateMediaStream('Local', 'local');
|
||||
|
||||
await validateMediaStream('Local Recorder', 'local-recorder');
|
||||
|
||||
await validateMediaStream('Remote Recorder', 'remote-recorder');
|
||||
|
||||
await validateMediaStream('Remote', 'remote');
|
||||
|
||||
await validateMediaStream('Low Resolution', 'low-resolution');
|
||||
|
||||
await this.validate(console, 'Audio Codecs', async () => {
|
||||
const vsos = await device.getVideoStreamOptions();
|
||||
|
||||
let codec: string | undefined;
|
||||
const codecs = new Set<string>();
|
||||
for (const vso of vsos) {
|
||||
if (vso.audio?.codec) {
|
||||
codec = vso.audio.codec;
|
||||
codecs.add(vso.audio.codec);
|
||||
}
|
||||
}
|
||||
|
||||
if (codecs.size > 1) {
|
||||
this.warnStep(console, `Mismatched audio codecs detected.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!codec)
|
||||
return;
|
||||
|
||||
if (codec !== 'pcm_mulaw' && codec !== 'aac' && codec !== 'opus') {
|
||||
this.warnStep(console, `Audio codec is ${codec}. pcm_mulaw, aac, or opus is recommended.`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async validateSystem() {
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
this.console.log('System Validation');
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
|
||||
const nvrPlugin = sdk.systemManager.getDeviceById('@scrypted/nvr');
|
||||
const cloudPlugin = sdk.systemManager.getDeviceById('@scrypted/cloud');
|
||||
const hasCUDA = process.env.NVIDIA_VISIBLE_DEVICES && process.env.NVIDIA_DRIVER_CAPABILITIES;
|
||||
const onnxPlugin = sdk.systemManager.getDeviceById<Settings & ObjectDetection>('@scrypted/onnx');
|
||||
const openvinoPlugin = sdk.systemManager.getDeviceById<Settings & ObjectDetection>('@scrypted/openvino');
|
||||
|
||||
await this.validate(this.console, 'Scrypted Installation', async () => {
|
||||
const e = process.env.SCRYPTED_INSTALL_ENVIRONMENT;
|
||||
if (process.platform !== 'linux') {
|
||||
if (e !== 'electron')
|
||||
this.warnStep(this.console, 'Upgrading to the Scrypted Desktop application is recommened for Windows and macOS.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (e !== 'docker' && e !== 'lxc' && e !== 'ha' && e !== 'lxc-docker')
|
||||
throw new Error('Unrecognized Linux installation. Installation via Docker image or the official Proxmox LXC script (not tteck) is recommended: https://docs.scrypted.app/installation');
|
||||
});
|
||||
|
||||
await this.validate(this.console, 'IPv4 (jsonip.com)', httpFetch({
|
||||
url: 'https://jsonip.com',
|
||||
family: 4,
|
||||
responseType: 'json',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.ip));
|
||||
|
||||
await this.validate(this.console, 'IPv6 (jsonip.com)', httpFetch({
|
||||
url: 'https://jsonip.com',
|
||||
family: 6,
|
||||
responseType: 'json',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.ip));
|
||||
|
||||
await this.validate(this.console, 'IPv4 (wtfismyip.com)', httpFetch({
|
||||
url: 'https://wtfismyip.com/text',
|
||||
family: 4,
|
||||
responseType: 'text',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.trim()));
|
||||
|
||||
await this.validate(this.console, 'IPv6 (wtfismyip.com)', httpFetch({
|
||||
url: 'https://wtfismyip.com/text',
|
||||
family: 6,
|
||||
responseType: 'text',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.trim()));
|
||||
|
||||
await this.validate(this.console, 'Scrypted Server Address', async () => {
|
||||
const addresses = await sdk.endpointManager.getLocalAddresses();
|
||||
const hasIPv4 = addresses?.find(address => net.isIPv4(address));
|
||||
const hasIPv6 = addresses?.find(address => net.isIPv6(address));
|
||||
if (addresses?.length)
|
||||
this.warnStep(this.console, addresses.join(', '));
|
||||
if (!hasIPv4)
|
||||
throw new Error('Scrypted Settings IPv4 address not set.');
|
||||
if (!hasIPv6)
|
||||
throw new Error('Scrypted Settings IPv6 address not set.');
|
||||
});
|
||||
|
||||
await this.validate(this.console, 'CPU Count', async () => {
|
||||
if (os.cpus().length < 2)
|
||||
throw new Error('CPU Count is too low. 4 CPUs are recommended.');
|
||||
return os.cpus().length;
|
||||
});
|
||||
|
||||
await this.validate(this.console, 'Memory', async () => {
|
||||
if (!nvrPlugin) {
|
||||
if (os.totalmem() < 8 * 1024 * 1024 * 1024)
|
||||
throw new Error('Memory is too low. 8GB is recommended.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (os.totalmem() < 14 * 1024 * 1024 * 1024)
|
||||
throw new Error('Memory is too low. 16GB is recommended for NVR.');
|
||||
return Math.floor(os.totalmem() / 1024 / 1024 / 1024) + " GB";
|
||||
});
|
||||
|
||||
if (process.platform === 'linux' && nvrPlugin) {
|
||||
// ensure /dev/dri/renderD128 or /dev/dri/renderD129 is available
|
||||
await this.validate(this.console, 'GPU Passthrough', async () => {
|
||||
if (!fs.existsSync('/dev/dri/renderD128') && !fs.existsSync('/dev/dri/renderD129'))
|
||||
throw new Error('GPU device unvailable or not passed through to container. (/dev/dri/renderD128, /dev/dri/renderD129)');
|
||||
// also check /dev/kfd for AMD CPU
|
||||
const amdCPU = os.cpus().find(c => c.model.includes('AMD'));
|
||||
if (amdCPU && !fs.existsSync('/dev/kfd'))
|
||||
throw new Error('GPU device unvailable or not passed through to container. (/dev/kfd)');
|
||||
});
|
||||
}
|
||||
|
||||
await this.validate(this.console, 'Cloud Plugin', async () => {
|
||||
if (!cloudPlugin) {
|
||||
this.warnStep(this.console, 'Cloud plugin not installed. Consider installing for remote access.');
|
||||
return;
|
||||
}
|
||||
|
||||
const logo = await httpFetch({
|
||||
url: 'https://home.scrypted.app/_punch/web_hi_res_512.png',
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
const mo = await sdk.mediaManager.createMediaObject(logo.body, 'image/png');
|
||||
const url = await sdk.mediaManager.convertMediaObjectToUrl(mo, 'image/png');
|
||||
|
||||
const logoCheck = await httpFetch({
|
||||
url,
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
if (Buffer.compare(logo.body, logoCheck.body))
|
||||
throw new Error('Invalid response received.');
|
||||
|
||||
const shortUrl: any = await sdk.mediaManager.convertMediaObject(mo, ScryptedMimeTypes.Url + ";short-lived=true");
|
||||
const shortLogoCheck = await httpFetch({
|
||||
url: shortUrl.toString(),
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
if (Buffer.compare(logo.body, shortLogoCheck.body))
|
||||
throw new Error('Invalid response received from short lived URL.');
|
||||
});
|
||||
|
||||
if ((hasCUDA || process.platform === 'win32') && onnxPlugin) {
|
||||
await this.validate(this.console, 'ONNX Plugin', async () => {
|
||||
const settings = await onnxPlugin.getSettings();
|
||||
const executionDevice = settings.find(s => s.key === 'execution_device');
|
||||
if (executionDevice?.value?.toString().includes('CPU'))
|
||||
this.warnStep(this.console, 'GPU device unvailable or not passed through to container.');
|
||||
|
||||
const zidane = await sdk.mediaManager.createMediaObjectFromUrl('https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg');
|
||||
const detected = await onnxPlugin.detectObjects(zidane);
|
||||
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
|
||||
if (!personFound)
|
||||
throw new Error('Person not detected in test image.');
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasCUDA && openvinoPlugin && (process.platform !== 'win32' || !onnxPlugin)) {
|
||||
await this.validate(this.console, 'OpenVINO Plugin', async () => {
|
||||
const settings = await openvinoPlugin.getSettings();
|
||||
const availbleDevices = settings.find(s => s.key === 'available_devices');
|
||||
if (!availbleDevices?.value?.toString().includes('GPU'))
|
||||
this.warnStep(this.console, 'GPU device unvailable or not passed through to container.');
|
||||
|
||||
const zidane = await sdk.mediaManager.createMediaObjectFromUrl('https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg');
|
||||
const detected = await openvinoPlugin.detectObjects(zidane);
|
||||
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
|
||||
if (!personFound)
|
||||
throw new Error('Person not detected in test image.');
|
||||
});
|
||||
}
|
||||
|
||||
if (nvrPlugin) {
|
||||
await this.validate(this.console, "GPU Decode", async () => {
|
||||
const ffmpegPath = await sdk.mediaManager.getFFmpegPath();
|
||||
let hasVaapi = false;
|
||||
{
|
||||
const args = [
|
||||
'-y',
|
||||
'-hwaccel', 'auto',
|
||||
'-i', 'https://github.com/koush/scrypted-sample-cameraprovider/raw/main/fs/dog.mp4',
|
||||
'-f', 'rawvideo',
|
||||
'pipe:3',
|
||||
];
|
||||
const cp = child_process.spawn(ffmpegPath, args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const deferred = new Deferred<void>();
|
||||
deferred.promise.catch(() => { }).finally(() => safeKillFFmpeg(cp));
|
||||
cp.stdio[3]?.on('data', () => { });
|
||||
|
||||
cp.stderr!.on('data', data => {
|
||||
const str = data.toString();
|
||||
hasVaapi ||= str.includes('Using auto hwaccel type vaapi');
|
||||
|
||||
if (str.includes('nv12'))
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
deferred.reject(new Error('GPU Decode timed out.'));
|
||||
}, 5000);
|
||||
|
||||
await deferred.promise;
|
||||
}
|
||||
|
||||
if (!hasVaapi || !openvinoPlugin)
|
||||
return;
|
||||
|
||||
{
|
||||
const args = [
|
||||
'-y',
|
||||
'-init_hw_device', 'vaapi=renderD128:/dev/dri/renderD128',
|
||||
'-hwaccel', 'vaapi',
|
||||
'-hwaccel_output_format', 'vaapi',
|
||||
'-i', 'https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg',
|
||||
'-vf', 'format=nv12,hwupload,scale_vaapi=w=320:h=-2,hwdownload,format=nv12',
|
||||
'-f', 'mjpeg',
|
||||
'-frames:v', '1',
|
||||
'pipe:3',
|
||||
];
|
||||
|
||||
const cp = child_process.spawn(ffmpegPath, args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
let std = '';
|
||||
cp.stderr!.on('data', data => {
|
||||
std += data.toString();
|
||||
});
|
||||
|
||||
cp.stdout!.on('data', data => {
|
||||
std += data.toString();
|
||||
});
|
||||
|
||||
const buffers: Buffer[] = [];
|
||||
cp.stdio[3]?.on('data', buffer => {
|
||||
buffers.push(buffer);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
safeKillFFmpeg(cp)
|
||||
}, 5000);
|
||||
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode) {
|
||||
this.warnStep(this.console, std);
|
||||
throw new Error('GPU Transform failed.');
|
||||
}
|
||||
|
||||
const jpeg = Buffer.concat(buffers);
|
||||
const zidane = await sdk.mediaManager.createMediaObject(jpeg, 'image/jpeg');
|
||||
const image = await sdk.mediaManager.convertMediaObject<Image>(zidane, ScryptedMimeTypes.Image);
|
||||
if (image.width !== 320)
|
||||
throw new Error('Unexpected image with from GPU transform.')
|
||||
const detected = await openvinoPlugin.detectObjects(zidane);
|
||||
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
|
||||
if (!personFound)
|
||||
throw new Error('Person not detected in test image.');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
await this.validate(this.console, 'Deprecated Plugins', async () => {
|
||||
const defunctPlugins = [
|
||||
'@scrypted/electron-core',
|
||||
'@scrypted/opencv',
|
||||
'@scrypted/python-codecs',
|
||||
'@scrypted/pam-diff',
|
||||
];
|
||||
let found = false;
|
||||
|
||||
for (const plugin of defunctPlugins) {
|
||||
const pluginDevice = sdk.systemManager.getDeviceById(plugin);
|
||||
if (pluginDevice) {
|
||||
this.warnStep(this.console, `Scrypted NVR users can remove: ${plugin}`);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
throw new Error('Deprecated plugins found.');
|
||||
});
|
||||
}
|
||||
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
this.console.log('System Validation Complete');
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
}
|
||||
}
|
||||
|
||||
export default DiagnosticsPlugin;
|
||||
14
plugins/diagnostics/tsconfig.json
Normal file
14
plugins/diagnostics/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"target": "ES2021",
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "scrypted-server",
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
}
|
||||
4
plugins/google-device-access/package-lock.json
generated
4
plugins/google-device-access/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/google-device-access",
|
||||
"version": "0.0.98",
|
||||
"version": "0.0.99",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/google-device-access",
|
||||
"version": "0.0.98",
|
||||
"version": "0.0.99",
|
||||
"dependencies": {
|
||||
"@googleapis/smartdevicemanagement": "^1.0.0",
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -49,5 +49,5 @@
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/node": "^20.4.1"
|
||||
},
|
||||
"version": "0.0.98"
|
||||
"version": "0.0.99"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"module": "Node16",
|
||||
"target": "ES2021",
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
|
||||
@@ -111,6 +111,12 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
|
||||
hide: true,
|
||||
description: 'The last home hub to request a recording. Internally used to determine if a streaming request is coming from remote wifi.',
|
||||
},
|
||||
autoAdd: {
|
||||
title: "Auto enable",
|
||||
description: "Automatically enable this mixin on new devices.",
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
});
|
||||
mergedDevices = new Set<string>();
|
||||
|
||||
@@ -218,7 +224,8 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
|
||||
|
||||
try {
|
||||
const mixins = (device.mixins || []).slice();
|
||||
if (!mixins.includes(this.id)) {
|
||||
const autoAdd = this.storageSettings.values.autoAdd ?? true;
|
||||
if (!mixins.includes(this.id) && autoAdd) {
|
||||
// don't sync this by default, as it's solely for automations
|
||||
if (device.type === ScryptedDeviceType.Notifier)
|
||||
continue;
|
||||
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.43",
|
||||
"version": "0.1.47",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.43",
|
||||
"version": "0.1.47",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.43",
|
||||
"version": "0.1.47",
|
||||
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -162,7 +162,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
getCurrentSettings() {
|
||||
const settings = this.model.settings;
|
||||
if (!settings)
|
||||
return { id : this.id };
|
||||
return { id: this.id };
|
||||
|
||||
const ret: { [key: string]: any } = {};
|
||||
for (const setting of settings) {
|
||||
@@ -334,13 +334,11 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
const stream = await this.cameraDevice.getVideoStream({
|
||||
prebuffer: this.model.prebuffer,
|
||||
destination,
|
||||
// ask rebroadcast to mute audio, not needed.
|
||||
audio: null,
|
||||
});
|
||||
|
||||
if (this.model.decoder) {
|
||||
if (!options?.suppress)
|
||||
this.console.log(this.objectDetection.name, '(with builtin decoder)');
|
||||
this.console.log(this.objectDetection.name, '(with builtin decoder)');
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -419,7 +417,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
const frameGenerator = this.model.decoder ? undefined : this.getFrameGenerator();
|
||||
for await (const detected of
|
||||
await sdk.connectRPCObject(
|
||||
await this.objectDetection.generateObjectDetections(
|
||||
await this.objectDetection.generateObjectDetections(
|
||||
await this.createFrameGenerator(
|
||||
frameGenerator,
|
||||
options,
|
||||
@@ -458,10 +456,10 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
if (!this.hasMotionType) {
|
||||
this.plugin.trackDetection();
|
||||
|
||||
// const numZonedDetections = zonedDetections.filter(d => d.className !== 'motion').length;
|
||||
// const numOriginalDetections = originalDetections.filter(d => d.className !== 'motion').length;
|
||||
// if (numZonedDetections !== numOriginalDetections)
|
||||
// this.console.log('Zone filtered detections:', numZonedDetections - numOriginalDetections);
|
||||
const numZonedDetections = zonedDetections.filter(d => d.className !== 'motion').length;
|
||||
const numOriginalDetections = originalDetections.filter(d => d.className !== 'motion').length;
|
||||
if (numZonedDetections !== numOriginalDetections)
|
||||
currentDetections.set('filtered', (currentDetections.get('filtered') || 0) + 1);
|
||||
|
||||
for (const d of detected.detected.detections) {
|
||||
currentDetections.set(d.className, Math.max(currentDetections.get(d.className) || 0, d.score));
|
||||
@@ -612,7 +610,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
this.detections.set(detectionId, detectionInput);
|
||||
setTimeout(() => {
|
||||
this.detections.delete(detectionId);
|
||||
}, 2000);
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
async getNativeObjectTypes(): Promise<ObjectDetectionTypes> {
|
||||
@@ -661,12 +659,11 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
frameGenerator = this.plugin.storageSettings.values.defaultDecoder || 'Default';
|
||||
|
||||
const pipelines = getAllDevices().filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator));
|
||||
const webcodec = process.env.SCRYPTED_INSTALL_ENVIRONMENT === 'electron' ? sdk.systemManager.getDeviceById('@scrypted/electron-core', 'webcodec') : undefined;
|
||||
const webassembly = sdk.systemManager.getDeviceById('@scrypted/nvr', 'decoder') || undefined;
|
||||
const gstreamer = sdk.systemManager.getDeviceById('@scrypted/python-codecs', 'gstreamer') || undefined;
|
||||
const libav = sdk.systemManager.getDeviceById('@scrypted/python-codecs', 'libav') || undefined;
|
||||
const ffmpeg = sdk.systemManager.getDeviceById('@scrypted/objectdetector', 'ffmpeg') || undefined;
|
||||
const use = pipelines.find(p => p.name === frameGenerator) || webcodec || webassembly || gstreamer || libav || ffmpeg;
|
||||
const use = pipelines.find(p => p.name === frameGenerator) || webassembly || gstreamer || libav || ffmpeg;
|
||||
return use.id;
|
||||
}
|
||||
|
||||
@@ -1060,6 +1057,10 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
checkHasEnabledMixin(device: ScryptedDevice): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
pruneOldStatistics() {
|
||||
const now = Date.now();
|
||||
for (const [k, v] of this.objectDetectionStatistics.entries()) {
|
||||
|
||||
5
plugins/onnx/.vscode/launch.json
vendored
5
plugins/onnx/.vscode/launch.json
vendored
@@ -6,7 +6,7 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "${config:scrypted.debugHost}",
|
||||
@@ -21,9 +21,8 @@
|
||||
},
|
||||
{
|
||||
"localRoot": "${workspaceFolder}/src",
|
||||
"remoteRoot": "${config:scrypted.pythonRemoteRoot}"
|
||||
"remoteRoot": "."
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
7
plugins/onnx/.vscode/settings.json
vendored
7
plugins/onnx/.vscode/settings.json
vendored
@@ -1,12 +1,8 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
"scrypted.debugHost": "koushik-ubuntuvm",
|
||||
"scrypted.debugHost": "koushik-winvm",
|
||||
"scrypted.serverRoot": "/server",
|
||||
|
||||
// lxc
|
||||
// "scrypted.debugHost": "scrypted-server",
|
||||
// "scrypted.serverRoot": "/root/.scrypted",
|
||||
|
||||
// pi local installation
|
||||
// "scrypted.debugHost": "192.168.2.119",
|
||||
@@ -18,7 +14,6 @@
|
||||
// "scrypted.debugHost": "koushik-winvm",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
|
||||
@@ -4,3 +4,24 @@ This plugin adds object detection capabilities to any camera in Scrypted. Having
|
||||
|
||||
The ONNX Plugin should only be used if you are a Scrypted NVR user. It will provide no
|
||||
benefits to HomeKit, which does its own detection processing.
|
||||
|
||||
# Windows Setup
|
||||
|
||||
Windows setup requires several installation steps and system PATH variables to be set correctly. The NVIDIA installers does not do this correctly if older CUDA or CUDNN exists.
|
||||
|
||||
1. Install latest NVIDIA drivers.
|
||||
2. Install CUDA 12.x.
|
||||
3. Install CUDNN 9.x.
|
||||
4. Open a new Terminal.
|
||||
5. Verify CUDA_PATH environment is set.
|
||||
* The syste, CUDA_PATH can be set in Windows Advanced System Settings.
|
||||
6. Verify PATH contains the path to CUDNN\bin\12.x, where the `cudnn64_9.dll` file is located. Typically it will be somewhere like: `"C:\Program Files\NVIDIA\CUDNN\v9.4\bin\12.6\cudnn64_9.dll"`.
|
||||
* The system PATH can be set in Windows Advanced System Settings.
|
||||
7. Exit Scrypted.
|
||||
8. Reopen Scrypted.
|
||||
|
||||
# Linux Setup
|
||||
|
||||
1. Install NVIDIA drivers on host.
|
||||
2. Install CUDA and CUDNN.
|
||||
3. Follow the NVIDIA setup steps for the NVIDIA docker image. https://docs.scrypted.app/installation.html#linux-docker
|
||||
4
plugins/onnx/package-lock.json
generated
4
plugins/onnx/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.119",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.119",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -35,12 +35,18 @@
|
||||
"interfaces": [
|
||||
"DeviceProvider",
|
||||
"Settings",
|
||||
"ClusterForkInterface",
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview"
|
||||
]
|
||||
],
|
||||
"labels": {
|
||||
"require": [
|
||||
"@scrypted/onnx"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.110"
|
||||
"version": "0.1.119"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from ort import ONNXPlugin
|
||||
import predict
|
||||
|
||||
def create_scrypted_plugin():
|
||||
return ONNXPlugin()
|
||||
|
||||
async def fork():
|
||||
return predict.Fork(ONNXPlugin)
|
||||
|
||||
@@ -40,6 +40,7 @@ availableModels = [
|
||||
"scrypted_yolov8n_320",
|
||||
]
|
||||
|
||||
|
||||
def parse_labels(names):
|
||||
j = ast.literal_eval(names)
|
||||
ret = {}
|
||||
@@ -47,11 +48,15 @@ def parse_labels(names):
|
||||
ret[int(k)] = v
|
||||
return ret
|
||||
|
||||
|
||||
class ONNXPlugin(
|
||||
PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings, scrypted_sdk.DeviceProvider
|
||||
PredictPlugin,
|
||||
scrypted_sdk.BufferConverter,
|
||||
scrypted_sdk.Settings,
|
||||
scrypted_sdk.DeviceProvider,
|
||||
):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
def __init__(self, nativeId: str | None = None, forked: bool = False):
|
||||
super().__init__(nativeId=nativeId, forked=forked)
|
||||
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
if model == "Default" or model not in availableModels:
|
||||
@@ -67,7 +72,11 @@ class ONNXPlugin(
|
||||
|
||||
print(f"model {model}")
|
||||
|
||||
onnxmodel = model if self.scrypted_yolo_nas else "best" if self.scrypted_model else model
|
||||
onnxmodel = (
|
||||
model
|
||||
if self.scrypted_yolo_nas
|
||||
else "best" if self.scrypted_model else model
|
||||
)
|
||||
|
||||
model_version = "v3"
|
||||
onnxfile = self.downloadFile(
|
||||
@@ -83,30 +92,37 @@ class ONNXPlugin(
|
||||
deviceIds = ["0"]
|
||||
self.deviceIds = deviceIds
|
||||
|
||||
compiled_models = []
|
||||
self.compiled_models = {}
|
||||
compiled_models: list[onnxruntime.InferenceSession] = []
|
||||
self.compiled_models: dict[str, onnxruntime.InferenceSession] = {}
|
||||
self.provider = "Unknown"
|
||||
|
||||
try:
|
||||
for deviceId in deviceIds:
|
||||
sess_options = onnxruntime.SessionOptions()
|
||||
|
||||
providers: list[str] = []
|
||||
if sys.platform == 'darwin':
|
||||
if sys.platform == "darwin":
|
||||
providers.append("CoreMLExecutionProvider")
|
||||
|
||||
if ('linux' in sys.platform or 'win' in sys.platform) and (platform.machine() == 'x86_64' or platform.machine() == 'AMD64'):
|
||||
if ("linux" in sys.platform or "win" in sys.platform) and (
|
||||
platform.machine() == "x86_64" or platform.machine() == "AMD64"
|
||||
):
|
||||
deviceId = int(deviceId)
|
||||
providers.append(("CUDAExecutionProvider", { "device_id": deviceId }))
|
||||
providers.append(("CUDAExecutionProvider", {"device_id": deviceId}))
|
||||
|
||||
providers.append('CPUExecutionProvider')
|
||||
providers.append("CPUExecutionProvider")
|
||||
|
||||
compiled_model = onnxruntime.InferenceSession(onnxfile, sess_options=sess_options, providers=providers)
|
||||
compiled_model = onnxruntime.InferenceSession(
|
||||
onnxfile, sess_options=sess_options, providers=providers
|
||||
)
|
||||
compiled_models.append(compiled_model)
|
||||
|
||||
input = compiled_model.get_inputs()[0]
|
||||
self.model_dim = input.shape[2]
|
||||
self.input_name = input.name
|
||||
self.labels = parse_labels(compiled_model.get_modelmeta().custom_metadata_map['names'])
|
||||
self.labels = parse_labels(
|
||||
compiled_model.get_modelmeta().custom_metadata_map["names"]
|
||||
)
|
||||
|
||||
except:
|
||||
import traceback
|
||||
@@ -121,7 +137,15 @@ class ONNXPlugin(
|
||||
thread_name = threading.current_thread().name
|
||||
interpreter = compiled_models.pop()
|
||||
self.compiled_models[thread_name] = interpreter
|
||||
print('Runtime initialized on thread {}'.format(thread_name))
|
||||
# remove CPUExecutionProider from providers
|
||||
providers = interpreter.get_providers()
|
||||
if not len(providers):
|
||||
providers = ["CPUExecutionProvider"]
|
||||
if "CPUExecutionProvider" in providers:
|
||||
providers.remove("CPUExecutionProvider")
|
||||
# join the remaining providers string
|
||||
self.provider = ", ".join(providers)
|
||||
print("Runtime initialized on thread {}".format(thread_name))
|
||||
|
||||
self.executor = concurrent.futures.ThreadPoolExecutor(
|
||||
initializer=executor_initializer,
|
||||
@@ -134,9 +158,13 @@ class ONNXPlugin(
|
||||
thread_name_prefix="onnx-prepare",
|
||||
)
|
||||
|
||||
self.executor.submit(lambda: None)
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
if not self.forked:
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
@@ -145,6 +173,7 @@ class ONNXPlugin(
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "ONNX Face Recognition",
|
||||
@@ -157,6 +186,7 @@ class ONNXPlugin(
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "ONNX Text Recognition",
|
||||
@@ -206,12 +236,12 @@ class ONNXPlugin(
|
||||
"key": "execution_device",
|
||||
"title": "Execution Device",
|
||||
"readonly": True,
|
||||
"value": onnxruntime.get_device(),
|
||||
}
|
||||
"value": self.provider,
|
||||
},
|
||||
]
|
||||
|
||||
async def putSetting(self, key: str, value: SettingValue):
|
||||
if (key == 'deviceIds'):
|
||||
if key == "deviceIds":
|
||||
value = json.dumps(value)
|
||||
self.storage.setItem(key, value)
|
||||
await self.onDeviceEvent(scrypted_sdk.ScryptedInterface.Settings.value, None)
|
||||
@@ -225,7 +255,7 @@ class ONNXPlugin(
|
||||
return [self.model_dim, self.model_dim]
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
def prepare():
|
||||
def prepare():
|
||||
im = np.array(input)
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
@@ -235,7 +265,7 @@ class ONNXPlugin(
|
||||
|
||||
def predict(input_tensor):
|
||||
compiled_model = self.compiled_models[threading.current_thread().name]
|
||||
output_tensors = compiled_model.run(None, { self.input_name: input_tensor })
|
||||
output_tensors = compiled_model.run(None, {self.input_name: input_tensor})
|
||||
if self.scrypted_yolov10:
|
||||
return yolo.parse_yolov10(output_tensors[0][0])
|
||||
if self.scrypted_yolo_nas:
|
||||
|
||||
@@ -14,11 +14,6 @@ from predict.face_recognize import FaceRecognizeDetection
|
||||
|
||||
|
||||
class ONNXFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, plugin, nativeId: str | None = None):
|
||||
self.plugin = plugin
|
||||
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
onnxmodel = "best" if "scrypted" in model else model
|
||||
model_version = "v1"
|
||||
|
||||
@@ -14,11 +14,6 @@ from predict.text_recognize import TextRecognition
|
||||
|
||||
|
||||
class ONNXTextRecognition(TextRecognition):
|
||||
def __init__(self, plugin, nativeId: str | None = None):
|
||||
self.plugin = plugin
|
||||
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
onnxmodel = model
|
||||
model_version = "v4"
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
|
||||
numpy==1.26.4
|
||||
|
||||
# uncomment to require cuda 12, but most stuff is still targetting cuda 11.
|
||||
# however, stuff targetted for cuda 11 can still run on cuda 12.
|
||||
# --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
|
||||
onnxruntime-gpu==1.18.1; 'darwin' not in sys_platform and platform_machine != 'aarch64'
|
||||
onnxruntime-gpu==1.19.2; 'darwin' not in sys_platform and platform_machine != 'aarch64'
|
||||
# cpu and coreml execution provider
|
||||
onnxruntime; 'darwin' in sys_platform or platform_machine == 'aarch64'
|
||||
# nightly?
|
||||
# ort-nightly-gpu==1.17.3.dev20240409002
|
||||
|
||||
Pillow==10.3.0
|
||||
opencv-python==4.10.0.84
|
||||
opencv-python-headless==4.10.0.84
|
||||
|
||||
2
plugins/onvif/.vscode/settings.json
vendored
2
plugins/onvif/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
Submodule plugins/onvif/onvif deleted from 5641b572c4
4021
plugins/onvif/package-lock.json
generated
4021
plugins/onvif/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/onvif",
|
||||
"version": "0.1.23",
|
||||
"version": "0.1.28",
|
||||
"description": "ONVIF Camera Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"base-64": "^1.0.0",
|
||||
"md5": "^2.3.0",
|
||||
"onvif": "file:./onvif",
|
||||
"onvif": "^0.7.4",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -20,12 +20,53 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
],
|
||||
onPut: (ov, ptz: string[]) => {
|
||||
this.ptzCapabilities = {
|
||||
...this.ptzCapabilities,
|
||||
pan: ptz.includes('Pan'),
|
||||
tilt: ptz.includes('Tilt'),
|
||||
zoom: ptz.includes('Zoom'),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ptzMovementType: {
|
||||
title: 'PTZ Movement Type',
|
||||
description: 'The type of movement to use for PTZ commands by default.',
|
||||
type: 'string',
|
||||
choices: [
|
||||
'Default',
|
||||
PanTiltZoomMovement.Absolute,
|
||||
PanTiltZoomMovement.Relative,
|
||||
PanTiltZoomMovement.Continuous,
|
||||
],
|
||||
defaultValue: 'Default',
|
||||
},
|
||||
presets: {
|
||||
title: 'Presets',
|
||||
description: 'PTZ Presets in the format "key=name". Where key is the PTZ Preset identifier and name is a friendly name.',
|
||||
multiple: true,
|
||||
defaultValue: [],
|
||||
combobox: true,
|
||||
onPut: async (ov, presets: string[]) => {
|
||||
const caps = {
|
||||
...this.ptzCapabilities,
|
||||
presets: {},
|
||||
};
|
||||
for (const preset of presets) {
|
||||
const [key, name] = preset.split('=');
|
||||
caps.presets[key] = name;
|
||||
}
|
||||
this.ptzCapabilities = caps;
|
||||
},
|
||||
mapGet: () => {
|
||||
const presets = this.ptzCapabilities?.presets || {};
|
||||
return Object.entries(presets).map(([key, name]) => key + '=' + name);
|
||||
},
|
||||
},
|
||||
cachedPresets: {
|
||||
multiple: true,
|
||||
hide: true,
|
||||
json: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
});
|
||||
|
||||
constructor(options: SettingsMixinDeviceOptions<Settings>) {
|
||||
@@ -33,6 +74,30 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
|
||||
// force a read to set the state.
|
||||
this.storageSettings.values.ptz;
|
||||
|
||||
this.refreshPresets();
|
||||
|
||||
this.storageSettings.settings.presets.onGet = async () => {
|
||||
// getPresets is where the key is the name of the preset, and the value is the id.
|
||||
// kind of weird and backwards.
|
||||
const choices = Object.entries(this.storageSettings.values.cachedPresets).map(([name, key]) => key + '=' + name);
|
||||
return {
|
||||
choices,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async refreshPresets() {
|
||||
const client = await this.getClient();
|
||||
client.cam.getPresets({}, (e, result, xml) => {
|
||||
if (e) {
|
||||
this.console.error('failed to get presets', e);
|
||||
}
|
||||
else {
|
||||
this.console.log('presets', result);
|
||||
this.storageSettings.values.cachedPresets = result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getMixinSettings(): Promise<Setting[]> {
|
||||
@@ -55,19 +120,65 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
};
|
||||
}
|
||||
|
||||
if (command.movement === PanTiltZoomMovement.Absolute) {
|
||||
let movement = command.movement || this.storageSettings.values.ptzMovementType;
|
||||
if (movement === PanTiltZoomMovement.Absolute) {
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.absoluteMove({
|
||||
x: command.pan,
|
||||
y: command.tilt,
|
||||
zoom: command.zoom,
|
||||
speed: speed
|
||||
speed: speed,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
});
|
||||
})
|
||||
}
|
||||
else if (movement === PanTiltZoomMovement.Continuous) {
|
||||
let x = command.pan;
|
||||
let y = command.tilt;
|
||||
let zoom = command.zoom;
|
||||
if (command.speed?.pan)
|
||||
x *= command.speed.pan;
|
||||
if (command.speed?.tilt)
|
||||
y *= command.speed.tilt;
|
||||
if (command.speed?.zoom)
|
||||
zoom *= command.speed.zoom;
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.continuousMove({
|
||||
x: command.pan,
|
||||
y: command.tilt,
|
||||
zoom: command.zoom,
|
||||
timeout: command.timeout || 1000,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
else if (movement === PanTiltZoomMovement.Home) {
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.gotoHomePosition({
|
||||
speed: speed,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
});
|
||||
}
|
||||
else if (movement === PanTiltZoomMovement.Preset) {
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.gotoPreset({
|
||||
preset: command.preset,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
});
|
||||
}
|
||||
else {
|
||||
// relative movement is default.
|
||||
@@ -82,7 +193,7 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
plugins/opencv/.vscode/launch.json
vendored
4
plugins/opencv/.vscode/launch.json
vendored
@@ -6,7 +6,7 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "${config:scrypted.debugHost}",
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
{
|
||||
"localRoot": "${workspaceFolder}/src",
|
||||
"remoteRoot": "${config:scrypted.pythonRemoteRoot}"
|
||||
"remoteRoot": "."
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
9
plugins/opencv/.vscode/settings.json
vendored
9
plugins/opencv/.vscode/settings.json
vendored
@@ -5,16 +5,15 @@
|
||||
// "scrypted.serverRoot": "/home/pi/.scrypted",
|
||||
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "koushik-ubuntu",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
"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",
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
|
||||
4
plugins/opencv/package-lock.json
generated
4
plugins/opencv/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.91",
|
||||
"version": "0.0.92",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.91",
|
||||
"version": "0.0.92",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.91"
|
||||
"version": "0.0.92"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
|
||||
numpy==1.26.4
|
||||
imutils>=0.5.0
|
||||
opencv-python==4.10.0.82
|
||||
opencv-python-headless==4.10.0.84
|
||||
Pillow==10.3.0
|
||||
|
||||
2
plugins/openvino/.vscode/launch.json
vendored
2
plugins/openvino/.vscode/launch.json
vendored
@@ -6,7 +6,7 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "${config:scrypted.debugHost}",
|
||||
|
||||
18
plugins/openvino/.vscode/settings.json
vendored
18
plugins/openvino/.vscode/settings.json
vendored
@@ -1,24 +1,6 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "scrypted-demo",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
|
||||
// proxmox installation
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
"scrypted.serverRoot": "/root/.scrypted",
|
||||
|
||||
// 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-winvm",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
|
||||
4
plugins/openvino/package-lock.json
generated
4
plugins/openvino/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.111",
|
||||
"version": "0.1.137",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.111",
|
||||
"version": "0.1.137",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -35,12 +35,18 @@
|
||||
"interfaces": [
|
||||
"DeviceProvider",
|
||||
"Settings",
|
||||
"ClusterForkInterface",
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview"
|
||||
]
|
||||
],
|
||||
"labels": {
|
||||
"require": [
|
||||
"@scrypted/openvino"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.111"
|
||||
"version": "0.1.137"
|
||||
}
|
||||
|
||||
@@ -33,4 +33,22 @@ async def ensureRGBAData(data: bytes, size: Tuple[int, int], format: str):
|
||||
return rgb.convert('RGBA')
|
||||
finally:
|
||||
rgb.close()
|
||||
return await to_thread(convert)
|
||||
return await to_thread(convert)
|
||||
|
||||
async def ensureYCbCrAData(data: bytes, size: Tuple[int, int], format: str):
|
||||
# if the format is already yuvj444p, just return the data as is.
|
||||
if format == 'yuvj444p':
|
||||
# return RGB as a hack to indicate the data is already yuv planar.
|
||||
return Image.frombuffer('RGB', size, data)
|
||||
|
||||
def convert():
|
||||
if format == 'rgb':
|
||||
tmp = Image.frombuffer('RGB', size, data)
|
||||
else:
|
||||
tmp = Image.frombuffer('RGBA', size, data)
|
||||
|
||||
try:
|
||||
return tmp.convert('YCbCr')
|
||||
finally:
|
||||
tmp.close()
|
||||
return await to_thread(convert)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user