mirror of
https://github.com/koush/scrypted.git
synced 2026-02-04 14:42:14 +00:00
Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b784399afa | ||
|
|
0f16568edb | ||
|
|
7ecee115a6 | ||
|
|
34eb2be551 | ||
|
|
27ff0c8c80 | ||
|
|
51c5df6802 | ||
|
|
328bd78771 | ||
|
|
3d2ae6384f | ||
|
|
e1ba16f708 | ||
|
|
6f47e39bf3 | ||
|
|
e38c3c975f | ||
|
|
9c75b074b5 | ||
|
|
299d926eae | ||
|
|
22d0ce4f82 | ||
|
|
53c2b7cb58 | ||
|
|
86548f6fa4 | ||
|
|
0e1e641f8f | ||
|
|
58e0a748c4 | ||
|
|
b4a58df53a | ||
|
|
b83b7ff559 | ||
|
|
de2173567e | ||
|
|
9c931b21dc | ||
|
|
5291afad6a | ||
|
|
e1ac1ace87 | ||
|
|
1f6f1a82aa | ||
|
|
70af66a875 | ||
|
|
b7bab5b2e2 | ||
|
|
5d5686a9e7 | ||
|
|
1eb5012e9b | ||
|
|
3574e72e4f | ||
|
|
b7ff4dfd5e | ||
|
|
e0ed953963 | ||
|
|
930690a4ba | ||
|
|
1aa4d45caa | ||
|
|
28fb2b0853 | ||
|
|
4fae4fba3b | ||
|
|
b72c8f59eb | ||
|
|
369ad59324 | ||
|
|
51ac5a1042 | ||
|
|
200c107e97 | ||
|
|
35139abe30 | ||
|
|
dc7f305687 | ||
|
|
2a479dd38a | ||
|
|
d32f9bb07a | ||
|
|
a33bed0b44 | ||
|
|
f9847f6f72 | ||
|
|
add53d07f3 | ||
|
|
db21159299 | ||
|
|
6fa7f06852 | ||
|
|
58387e5046 | ||
|
|
1589908698 | ||
|
|
d0183c29a8 | ||
|
|
99dcdd12cf | ||
|
|
b1861e4630 | ||
|
|
193bfce979 | ||
|
|
5b7cc826a6 | ||
|
|
8484d75e82 | ||
|
|
e8fef925bb | ||
|
|
fa200e1bbf | ||
|
|
df0991b882 | ||
|
|
93ff686000 | ||
|
|
6ae9a5618d | ||
|
|
c882b9a04e | ||
|
|
af4269be49 | ||
|
|
61ad99a3f6 | ||
|
|
d71bbf1824 | ||
|
|
74674dab00 | ||
|
|
247f860a23 | ||
|
|
a801fe1f4e | ||
|
|
6744851256 | ||
|
|
10569731aa | ||
|
|
4965b1f99a | ||
|
|
510250c60b | ||
|
|
8e33775b0e | ||
|
|
1077bd1f56 | ||
|
|
a485d8ae69 | ||
|
|
17f42762e7 | ||
|
|
49943a5408 | ||
|
|
585c638220 | ||
|
|
6767892c63 | ||
|
|
289555c03e | ||
|
|
a563e17c56 | ||
|
|
54c317b217 | ||
|
|
0df9c31480 | ||
|
|
19c8436256 | ||
|
|
b73526674a | ||
|
|
fd863f4ba3 | ||
|
|
634b65c216 | ||
|
|
548086403b | ||
|
|
867432cd82 | ||
|
|
b3cc914772 | ||
|
|
b297a4d3d6 | ||
|
|
8144588bcf | ||
|
|
f3265f5fb6 | ||
|
|
f686812f01 | ||
|
|
552787e06b | ||
|
|
3c4de5af39 | ||
|
|
e08df29373 | ||
|
|
1efb624681 | ||
|
|
09afc6c96c | ||
|
|
666d2903e4 | ||
|
|
24eb60bce1 | ||
|
|
d1951687be | ||
|
|
3c3c2c1610 | ||
|
|
0f9106c639 | ||
|
|
ab00ade016 | ||
|
|
6cfc3db05c | ||
|
|
95aa58ce38 | ||
|
|
0d88b4746b | ||
|
|
8c4beeb3a0 | ||
|
|
4846cfaddf | ||
|
|
4e14f7fd6f | ||
|
|
266be72606 | ||
|
|
6a1970c075 | ||
|
|
0575d98424 | ||
|
|
cdf42fc1a2 | ||
|
|
fc1fabc49e | ||
|
|
4e08daecb2 | ||
|
|
58b27805ba | ||
|
|
b37c6bbd06 | ||
|
|
8eca02d819 | ||
|
|
0efdb34114 | ||
|
|
1a25100de2 | ||
|
|
51e0a8836d | ||
|
|
562d0839b7 | ||
|
|
e3df6accea | ||
|
|
03d159a89c | ||
|
|
4ead4726a9 | ||
|
|
b06ef623b3 | ||
|
|
8edb157e2a | ||
|
|
155a1ceb38 | ||
|
|
1cb6212fc6 | ||
|
|
ea628a7130 |
11
.github/workflows/docker-common.yml
vendored
11
.github/workflows/docker-common.yml
vendored
@@ -77,13 +77,14 @@ jobs:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
build-nvidia:
|
||||
name: Push NVIDIA Docker image to Docker Hub
|
||||
build-vendor:
|
||||
name: Push Vendor Docker image to Docker Hub
|
||||
needs: build
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
BASE: ["noble"]
|
||||
VENDOR: ["nvidia", "intel"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
@@ -138,11 +139,11 @@ jobs:
|
||||
build-args: |
|
||||
BASE=ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-full
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile.nvidia
|
||||
file: install/docker/Dockerfile.${{ matrix.VENDOR }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted-common:${{ matrix.BASE }}-nvidia
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-nvidia
|
||||
koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.VENDOR }}
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.VENDOR }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
33
.github/workflows/docker.yml
vendored
33
.github/workflows/docker.yml
vendored
@@ -20,10 +20,11 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
BASE: [
|
||||
["noble-nvidia", ".s6", "noble-nvidia"],
|
||||
["noble-full", ".s6", "noble-full"],
|
||||
["noble-lite", "", "noble-lite"],
|
||||
# ["noble-lite", ".router", "noble-router"],
|
||||
["noble-nvidia", ".s6", "noble-nvidia", "nvidia"],
|
||||
["noble-intel", ".s6", "noble-intel", "intel"],
|
||||
["noble-full", ".s6", "noble-full", "full"],
|
||||
["noble-lite", "", "noble-lite", "lite"],
|
||||
["noble-lite", ".router", "noble-router", "router"],
|
||||
]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
@@ -94,19 +95,25 @@ jobs:
|
||||
file: install/docker/Dockerfile${{ matrix.BASE[1] }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
# when publishing a tag (beta or latest), platform and version, create some tags as follows.
|
||||
# using beta 0.0.1 as an example
|
||||
# koush/scrypted:v0.0.1-noble-full
|
||||
# koush/scrypted:beta
|
||||
# koush/scrypted:beta-nvidia|intel|full|router|lite
|
||||
|
||||
# using latest 0.0.2 as an example:
|
||||
# koush/scrypted:v0.0.2-noble-full
|
||||
# koush/scrypted:latest
|
||||
# koush/scrypted:nvidia|intel|full|router|lite
|
||||
tags: |
|
||||
${{ format('koush/scrypted:v{1}-{0}', matrix.BASE[2], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ format('koush/scrypted:v{0}-{1}', github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION, matrix.BASE[2]) }}
|
||||
${{ matrix.BASE[2] == 'noble-full' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-nvidia' && 'koush/scrypted:nvidia' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-full' && 'koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-lite' && matrix.BASE[1] == '' && 'koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-router' && 'koush/scrypted:router' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && format('koush/scrypted:{0}', matrix.BASE[3]) || '' }}
|
||||
${{ github.event.inputs.tag != 'latest' && format('koush/scrypted:{0}-{1}', github.event.inputs.tag, matrix.BASE[3]) }}
|
||||
|
||||
${{ format('ghcr.io/koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE[2] == 'noble-full' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-nvidia' && 'ghcr.io/koush/scrypted:nvidia' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-full' && 'ghcr.io/koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-lite' && matrix.BASE[1] == '' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[2] == 'noble-lite' && 'ghcr.io/koush/scrypted:router' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && format('ghcr.io/koush/scrypted:{0}', matrix.BASE[3]) || ''}}
|
||||
${{ github.event.inputs.tag != 'latest' && format('ghcr.io/koush/scrypted:{0}-{1}', github.event.inputs.tag, matrix.BASE[3]) || '' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
58
common/package-lock.json
generated
58
common/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/types": "^0.5.27",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
@@ -21,28 +22,29 @@
|
||||
},
|
||||
"../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.29",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.5",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-typescript": "^12.1.1",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.8",
|
||||
"babel-loader": "^9.2.1",
|
||||
"axios": "^1.10.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"openai": "^5.3.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.27.4",
|
||||
"rollup": "^4.43.0",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-loader": "^9.5.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.6.3",
|
||||
"webpack": "^5.96.1",
|
||||
"typescript": "^5.8.3",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
@@ -55,9 +57,9 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/node": "^24.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.11"
|
||||
"typedoc": "^0.28.5"
|
||||
}
|
||||
},
|
||||
"../sdk/node_modules/@ampproject/remapping": {
|
||||
@@ -3308,6 +3310,15 @@
|
||||
"resolved": "../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.5.27",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.27.tgz",
|
||||
"integrity": "sha512-1SAEa6Js1VeAzGtaCQXXpNc2Ty1ZB6aqqNLtsoPeeuNw+JlSdK42sX4wVnzKxkAOcS1WZiC1fj6DV9B/CNyGtA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"openai": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"dev": true,
|
||||
@@ -3393,6 +3404,27 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-5.8.2.tgz",
|
||||
"integrity": "sha512-8C+nzoHYgyYOXhHGN6r0fcb4SznuEn1R7YZMvlqDbnCuE0FM2mm3T1HiYW6WIcMS/F1Of2up/cSPjLPaWt0X9Q==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"openai": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"dev": true,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/types": "^0.5.27",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
|
||||
5
common/src/devices.ts
Normal file
5
common/src/devices.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { SystemManager } from '@scrypted/types';
|
||||
|
||||
export function getAllDevices<T>(systemManager: SystemManager) {
|
||||
return Object.keys(systemManager.getSystemState()).map(id => systemManager.getDeviceById<T>(id));
|
||||
}
|
||||
8
common/src/json.ts
Normal file
8
common/src/json.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
export function safeParseJson(value: string) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
@@ -93,8 +93,12 @@ export const H265_NAL_TYPE_AGG = 48;
|
||||
export const H265_NAL_TYPE_VPS = 32;
|
||||
export const H265_NAL_TYPE_SPS = 33;
|
||||
export const H265_NAL_TYPE_PPS = 34;
|
||||
export const H265_NAL_TYPE_IDR_N = 19;
|
||||
export const H265_NAL_TYPE_IDR_W = 20;
|
||||
export const H265_NAL_TYPE_BLA_W_LP = 16;
|
||||
export const H265_NAL_TYPE_BLA_W_RADL = 17;
|
||||
export const H265_NAL_TYPE_BLA_N_LP = 18;
|
||||
export const H265_NAL_TYPE_IDR_W_RADL = 19;
|
||||
export const H265_NAL_TYPE_IDR_N_LP = 20;
|
||||
export const H265_NAL_TYPE_CRA_NUT = 21;
|
||||
export const H265_NAL_TYPE_FU = 49;
|
||||
export const H265_NAL_TYPE_SEI_PREFIX = 39;
|
||||
export const H265_NAL_TYPE_SEI_SUFFIX = 40;
|
||||
@@ -252,6 +256,26 @@ export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fu
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function isH265KeyFrameRelatedInSet(naluTypes: Set<number>, allowCodecInfo = true) {
|
||||
if (naluTypes.has(H265_NAL_TYPE_IDR_N_LP)
|
||||
|| naluTypes.has(H265_NAL_TYPE_IDR_W_RADL)
|
||||
|| naluTypes.has(H265_NAL_TYPE_CRA_NUT)
|
||||
|| naluTypes.has(H265_NAL_TYPE_BLA_N_LP)
|
||||
|| naluTypes.has(H265_NAL_TYPE_BLA_W_LP)
|
||||
|| naluTypes.has(H265_NAL_TYPE_BLA_W_RADL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowCodecInfo) {
|
||||
if (naluTypes.has(H265_NAL_TYPE_VPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_SPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_PPS))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
export function createRtspParser(options?: StreamParserOptions): RtspStreamParser {
|
||||
let resolve: any;
|
||||
|
||||
@@ -283,12 +307,7 @@ export function createRtspParser(options?: StreamParserOptions): RtspStreamParse
|
||||
else if (streamChunk.type === 'h265') {
|
||||
const naluTypes = getStartedH265NaluTypes(streamChunk);
|
||||
|
||||
if (naluTypes.has(H265_NAL_TYPE_VPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_SPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_PPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_IDR_N)
|
||||
|| naluTypes.has(H265_NAL_TYPE_IDR_W)
|
||||
) {
|
||||
if (isH265KeyFrameRelatedInSet(naluTypes)) {
|
||||
return streamChunks.slice(prebufferIndex);
|
||||
}
|
||||
}
|
||||
@@ -670,9 +689,12 @@ export class RtspClient extends RtspBase {
|
||||
// @ts-ignore
|
||||
const { parseHTTPHeadersQuotedKeyValueSet } = await import('http-auth-utils/dist/utils');
|
||||
|
||||
const authedUrl = new URL(this.url);
|
||||
const username = decodeURIComponent(authedUrl.username);
|
||||
const password = decodeURIComponent(authedUrl.password);
|
||||
|
||||
if (this.wwwAuthenticate.includes('Basic')) {
|
||||
const parsedUrl = new URL(this.url);
|
||||
const hash = BASIC.computeHash({ username: parsedUrl.username, password: parsedUrl.password });
|
||||
const hash = BASIC.computeHash({ username, password });
|
||||
return `Basic ${hash}`;
|
||||
}
|
||||
|
||||
@@ -692,10 +714,6 @@ export class RtspClient extends RtspBase {
|
||||
REQUIRED_WWW_AUTHENTICATE_KEYS,
|
||||
) as DigestWWWAuthenticateData;
|
||||
|
||||
const authedUrl = new URL(this.url);
|
||||
const username = decodeURIComponent(authedUrl.username);
|
||||
const password = decodeURIComponent(authedUrl.password);
|
||||
|
||||
const strippedUrl = new URL(url.toString());
|
||||
strippedUrl.username = '';
|
||||
strippedUrl.password = '';
|
||||
|
||||
@@ -175,6 +175,8 @@ export type RTPMap = ReturnType<typeof parseRtpMap>;
|
||||
export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string) {
|
||||
const mlineType = mline.type;
|
||||
const match = rtpmap?.match(/a=rtpmap:([\d]+) (.*?)\/([\d]+)(\/([\d]+))?/);
|
||||
let channels = parseInt(match?.[5]) || undefined;
|
||||
let payloadType = parseInt(match?.[1]);
|
||||
|
||||
rtpmap = rtpmap?.toLowerCase();
|
||||
|
||||
@@ -222,14 +224,20 @@ export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string
|
||||
if (mline.payloadTypes?.includes(0)) {
|
||||
codec = 'pcm_mulaw';
|
||||
ffmpegEncoder = 'pcm_mulaw';
|
||||
payloadType = 0;
|
||||
channels = 1;
|
||||
}
|
||||
else if (mline.payloadTypes?.includes(8)) {
|
||||
codec = 'pcm_alaw';
|
||||
ffmpegEncoder = 'pcm_alaw';
|
||||
payloadType = 8;
|
||||
channels = 1;
|
||||
}
|
||||
else if (mline.payloadTypes?.includes(14)) {
|
||||
codec = 'mp3';
|
||||
ffmpegEncoder = 'mp3';
|
||||
payloadType = 14;
|
||||
channels = 2;
|
||||
}
|
||||
else {
|
||||
// ffmpeg seems to omit the rtpmap type for pcm alaw when creating sdp?
|
||||
@@ -239,17 +247,29 @@ export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string
|
||||
// https://en.wikipedia.org/wiki/RTP_payload_formats
|
||||
codec = 'pcm_alaw';
|
||||
ffmpegEncoder = 'pcm_alaw';
|
||||
payloadType = 8;
|
||||
channels = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// assigned payload types do not need to provide a clock, there is a default.
|
||||
let clock = parseInt(match?.[3]);
|
||||
if (!clock) {
|
||||
clock = undefined;
|
||||
if (codec === 'pcm_mulaw' || codec === 'pcm_alaw')
|
||||
clock = 8000;
|
||||
else if (codec === 'pcm_s16be')
|
||||
clock = 16000;
|
||||
}
|
||||
|
||||
return {
|
||||
line: rtpmap,
|
||||
codec,
|
||||
ffmpegEncoder,
|
||||
rawCodec: match?.[2],
|
||||
clock: parseInt(match?.[3]),
|
||||
channels: parseInt(match?.[5]) || undefined,
|
||||
payloadType: parseInt(match?.[1]),
|
||||
clock,
|
||||
channels,
|
||||
payloadType,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
external/ring-client-api
vendored
2
external/ring-client-api
vendored
Submodule external/ring-client-api updated: d9f51b8b6d...516e96a24e
@@ -1,6 +1,6 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "v0.130.1-noble-full"
|
||||
version: "v0.139.0-noble-full"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BASE="20-jammy-full"
|
||||
ARG BASE="noble-full"
|
||||
FROM ghcr.io/koush/scrypted-common:${BASE}
|
||||
|
||||
WORKDIR /
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# install script.
|
||||
################################################################
|
||||
ARG BASE="noble"
|
||||
FROM ubuntu:${BASE} as header
|
||||
FROM ubuntu:${BASE} AS header
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
@@ -61,7 +61,7 @@ RUN python3 -m pip install debugpy
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
FROM header as base
|
||||
FROM header AS base
|
||||
|
||||
# vulkan
|
||||
RUN apt -y install libvulkan1
|
||||
|
||||
9
install/docker/Dockerfile.intel
Normal file
9
install/docker/Dockerfile.intel
Normal file
@@ -0,0 +1,9 @@
|
||||
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
|
||||
FROM $BASE
|
||||
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="intel"
|
||||
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-oneapi.sh | bash
|
||||
# these paths must be updated if oneapi is updated via the install-intel-oneapi.sh script
|
||||
# note that the 2022.2 seems to be a typo in the intel script...?
|
||||
ENV LD_LIBRARY_PATH=/opt/intel/oneapi/tcm/1.4/lib:/opt/intel/oneapi/umf/0.11/lib:/opt/intel/oneapi/tbb/2022.2/env/../lib/intel64/gcc4.8:/opt/intel/oneapi/mkl/2025.2/lib:/opt/intel/oneapi/compiler/2025.2/opt/compiler/lib:/opt/intel/oneapi/compiler/2025.2/lib
|
||||
@@ -1,5 +1,7 @@
|
||||
ARG BASE="jammy"
|
||||
FROM ubuntu:${BASE} as header
|
||||
FROM ubuntu:${BASE} AS header
|
||||
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="lite"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
@@ -26,5 +28,3 @@ ENV SHELL="/bin/bash"
|
||||
RUN test -f "/usr/bin/python3" && test -f "/usr/bin/python3.12"
|
||||
ENV SCRYPTED_PYTHON_PATH="/usr/bin/python3"
|
||||
ENV SCRYPTED_PYTHON312_PATH="/usr/bin/python3.12"
|
||||
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="lite"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
|
||||
FROM $BASE
|
||||
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="nvidia"
|
||||
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES=all
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
ARG BASE="noble-lite"
|
||||
FROM ghcr.io/koush/scrypted-common:${BASE}
|
||||
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="router"
|
||||
|
||||
# tools
|
||||
RUN apt -y update && apt -y install nano net-tools dnsutils dnsmasq vlan bridge-utils netplan.io nftables isc-dhcp-client
|
||||
RUN apt -y update && apt -y install nano net-tools dnsutils dnsmasq vlan bridge-utils netplan.io nftables isc-dhcp-client cron
|
||||
RUN rm -f /etc/systemd/system/multi-user.target.wants/dnsmasq.service
|
||||
RUN rm -f /etc/systemd/system/sysinit.target.wants/systemd-resolved.service
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ RUN apt-get update && apt-get -y install \
|
||||
libavahi-compat-libdnssd-dev \
|
||||
xz-utils
|
||||
|
||||
# killall
|
||||
RUN apt -y install psmisc
|
||||
|
||||
# copy configurations and scripts
|
||||
COPY fs /
|
||||
|
||||
|
||||
@@ -45,10 +45,14 @@ services:
|
||||
# - SCRYPTED_DOCKER_AVAHI=true
|
||||
|
||||
# NVIDIA (Part 1 of 2)
|
||||
# runtime: nvidia
|
||||
# nvidia runtime: nvidia
|
||||
|
||||
# NVIDIA (Part 2 of 2) - Use NVIDIA image, and remove subsequent default image.
|
||||
# image: ghcr.io/koush/scrypted:nvidia
|
||||
# Valid images:
|
||||
# ghcr.io/koush/scrypted
|
||||
# ghcr.io/koush/scrypted:nvidia
|
||||
# ghcr.io/koush/scrypted:intel
|
||||
# ghcr.io/koush/scrypted:lite
|
||||
image: ghcr.io/koush/scrypted
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -72,12 +72,12 @@ apt-get install -y ocl-icd-libopencl1
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.35.30872.22
|
||||
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-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-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
|
||||
@@ -85,20 +85,17 @@ curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.3087
|
||||
dpkg -i *.deb
|
||||
rm -f *.deb
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.45.31740.9
|
||||
# https://github.com/intel/compute-runtime/releases
|
||||
# note that at time of commit, IGC supports ubuntu 24.04 only possibly due to their builder being on 24.04.
|
||||
IGC_BASE_VERSION=2.5.6
|
||||
IGC_VERSION=2_$IGC_BASE_VERSION+18417_amd64
|
||||
COMPUTE_VERSION=24.52.32224.5
|
||||
ZERO_GPU_VERSION=1.6.32224.5_amd64
|
||||
LIBIGDGMM_VERSION=22.5.5_amd64
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v$IGC_BASE_VERSION/intel-igc-core-$IGC_VERSION.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v$IGC_BASE_VERSION/intel-igc-opencl-$IGC_VERSION.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu-dbgsym_$ZERO_GPU_VERSION.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu_$ZERO_GPU_VERSION.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-opencl-icd-dbgsym_"$COMPUTE_VERSION"_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-opencl-icd_"$COMPUTE_VERSION"_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/libigdgmm12_$LIBIGDGMM_VERSION.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.12.5/intel-igc-core-2_2.12.5+19302_amd64.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.12.5/intel-igc-opencl-2_2.12.5+19302_amd64.deb
|
||||
# curl -O -L https://github.com/intel/compute-runtime/releases/download/25.22.33944.8/intel-ocloc-dbgsym_25.22.33944.8-0_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.22.33944.8/intel-ocloc_25.22.33944.8-0_amd64.deb
|
||||
# curl -O -L https://github.com/intel/compute-runtime/releases/download/25.22.33944.8/intel-opencl-icd-dbgsym_25.22.33944.8-0_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.22.33944.8/intel-opencl-icd_25.22.33944.8-0_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.22.33944.8/libigdgmm12_22.7.0_amd64.deb
|
||||
# curl -O -L https://github.com/intel/compute-runtime/releases/download/25.22.33944.8/libze-intel-gpu1-dbgsym_25.22.33944.8-0_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.22.33944.8/libze-intel-gpu1_25.22.33944.8-0_amd64.deb
|
||||
|
||||
set +e
|
||||
dpkg -i *.deb
|
||||
|
||||
@@ -38,15 +38,15 @@ set -e
|
||||
rm -rf /tmp/npu && mkdir -p /tmp/npu && cd /tmp/npu
|
||||
|
||||
# level zero must also be installed
|
||||
LEVEL_ZERO_VERSION=1.19.2
|
||||
LEVEL_ZERO_VERSION=1.22.4
|
||||
# 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
|
||||
|
||||
# npu driver
|
||||
# https://github.com/intel/linux-npu-driver
|
||||
NPU_VERSION=1.13.0
|
||||
NPU_VERSION_DATE=20250131-13074932693
|
||||
NPU_VERSION=1.19.0
|
||||
NPU_VERSION_DATE=20250707-16111289554
|
||||
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" ]
|
||||
|
||||
18
install/docker/install-intel-oneapi.sh
Normal file
18
install/docker/install-intel-oneapi.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
if [ "$(uname -m)" = "x86_64" ]
|
||||
then
|
||||
apt -y update
|
||||
apt -y install gpg
|
||||
|
||||
# download the key to system keyring
|
||||
curl -1sLf https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor --yes --output /usr/share/keyrings/oneapi-archive-keyring.gpg
|
||||
|
||||
# add signed entry to apt sources and configure the APT client to use Intel repository:
|
||||
echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | tee /etc/apt/sources.list.d/oneAPI.list
|
||||
|
||||
apt -y update
|
||||
apt -y install intel-oneapi-mkl-sycl-blas intel-oneapi-runtime-dnnl intel-oneapi-runtime-compilers
|
||||
else
|
||||
echo "NVIDIA graphics will not be installed on this architecture."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -36,7 +36,8 @@ curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --yes --dea
|
||||
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 cuda-drivers
|
||||
# cuda-drivers does not work with blackwell for some reason, container toolkit it broken IIRC.
|
||||
apt -y install nvidia-open
|
||||
apt -y install nvidia-container-toolkit
|
||||
|
||||
nvidia-ctk runtime configure --runtime=docker
|
||||
|
||||
@@ -23,7 +23,7 @@ then
|
||||
&& 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-12-6 libcublas-12-6 libcudnn9-cuda-12 cuda-libraries-12-6;
|
||||
&& apt install -y cuda-nvcc-12-9 libcublas-12-9 libcudnn9-cuda-12 cuda-libraries-12-9;
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
|
||||
@@ -13,6 +13,8 @@ then
|
||||
fi
|
||||
|
||||
function readyn() {
|
||||
echo
|
||||
echo
|
||||
if [ ! -z "$SCRYPTED_NONINTERACTIVE" ]
|
||||
then
|
||||
yn="y"
|
||||
@@ -51,6 +53,9 @@ rm -rf $SCRYPTED_HOME/install.json
|
||||
rm -rf $SCRYPTED_HOME/package.json
|
||||
rm -rf $SCRYPTED_HOME/package-lock.json
|
||||
|
||||
# must get this value as grep returns non zero if empty
|
||||
HAS_NVIDIA=$(lspci | grep -i nvidia)
|
||||
|
||||
set -e
|
||||
cd $SCRYPTED_HOME
|
||||
|
||||
@@ -93,6 +98,24 @@ else
|
||||
sudo apt -y purge apparmor || true
|
||||
fi
|
||||
|
||||
if [ ! -z "$HAS_NVIDIA" ]
|
||||
then
|
||||
readyn "NVIDIA GPU detected. Use NVIDIA image for GPU acceleration?"
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
readyn "NVIDIA image requires the NVIDIA Drivers and Container Toolkit to be installed. This script can install them for you. Install NVIDIA Drivers and Container Toolkit for GPU acceleration?"
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
curl -fsSL https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-container-toolkit.sh -o install-nvidia-container-toolkit.sh
|
||||
chmod +x install-nvidia-container-toolkit.sh
|
||||
./install-nvidia-container-toolkit.sh
|
||||
rm install-nvidia-container-toolkit.sh
|
||||
fi
|
||||
sed -i 's/'#' nvidia //g' $DOCKER_COMPOSE_YML
|
||||
sed -i 's/ghcr.io\/koush\/scrypted/ghcr.io\/koush\/scrypted:nvidia/g' $DOCKER_COMPOSE_YML
|
||||
fi
|
||||
fi
|
||||
|
||||
readyn "Install avahi-daemon? This is the recommended for reliable HomeKit discovery and pairing."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
FROM header as base
|
||||
FROM header AS base
|
||||
|
||||
# vulkan
|
||||
RUN apt -y install libvulkan1
|
||||
|
||||
@@ -42,9 +42,6 @@ RUN brew update
|
||||
|
||||
# in sequoia, brew node is unusable because it is not signed and can't access local network unless run as root.
|
||||
# https://developer.apple.com/forums/thread/766270
|
||||
# RUN_IGNORE brew install node@20
|
||||
# NODE_PATH=$(brew --prefix node@20)
|
||||
# NODE_BIN_PATH=$NODE_PATH/bin
|
||||
RUN_IGNORE curl -L https://nodejs.org/dist/v22.14.0/node-v22.14.0.pkg -o /tmp/node.pkg
|
||||
RUN_IGNORE sudo installer -pkg /tmp/node.pkg -target /
|
||||
NODE_PATH=/usr/local # used to pass var test
|
||||
@@ -88,13 +85,13 @@ RUN mkdir -p ~/Library/LaunchAgents
|
||||
|
||||
if [ ! -d "$NODE_PATH" ]
|
||||
then
|
||||
echo "Unable to determine node@20 path."
|
||||
echo "Unable to determine node path."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$NODE_BIN_PATH" ]
|
||||
then
|
||||
echo "Unable to determine node@20 bin path."
|
||||
echo "Unable to determine node bin path."
|
||||
echo "$NODE_BIN_PATH does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -19,7 +19,7 @@ sc.exe stop scrypted.exe
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
|
||||
|
||||
# Install node.js
|
||||
choco upgrade -y nodejs-lts --version=20.18.0
|
||||
choco upgrade -y nodejs-lts --version=22.15.0
|
||||
|
||||
# Install VC Redist, which is necessary for portable python
|
||||
choco install -y vcredist140
|
||||
@@ -34,7 +34,7 @@ $SCRYPTED_WINDOWS_PYTHON_VERSION="-3.9"
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||
|
||||
# Workaround Windows Node no longer creating %APPDATA%\npm which causes npx to fail
|
||||
# Fixed in newer versions of NPM but not the one bundled with Node 20
|
||||
# Fixed in newer versions of NPM but not the one bundled with Node 2x
|
||||
# https://github.com/nodejs/node/issues/53538
|
||||
npm i -g npm
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function readyn() {
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.137.0
|
||||
SCRYPTED_VERSION=v0.139.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
|
||||
@@ -27,7 +27,8 @@ async function getAuth(options: AuthFetchOptions, url: string | URL, method: str
|
||||
++credential.count;
|
||||
const nc = ('00000000' + credential.count).slice(-8);
|
||||
const cnonce = [...Array(24)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
|
||||
const uri = new URL(url).pathname;
|
||||
const parsedURL = new URL(url);
|
||||
const uri = parsedURL.pathname + parsedURL.search;
|
||||
|
||||
const { DIGEST, buildAuthorizationHeader } = await import('http-auth-utils');
|
||||
|
||||
|
||||
94
packages/client/package-lock.json
generated
94
packages/client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/client",
|
||||
"version": "1.3.13",
|
||||
"version": "1.3.17",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/client",
|
||||
"version": "1.3.13",
|
||||
"version": "1.3.17",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"engine.io-client": "^6.6.3",
|
||||
@@ -15,13 +15,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/ws": "^8.18.0",
|
||||
"@types/node": "^24.0.10",
|
||||
"@types/ws": "^8.18.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.2"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@scrypted/types": "^0.5.12"
|
||||
"@scrypted/types": "^0.5.23"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -83,11 +83,59 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.5.12",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.12.tgz",
|
||||
"integrity": "sha512-nTwcMHZyH3nXThL22eNcVw7OjSyL5qoTgUay6K7y43HKz1mBnFEmIUkW8eLdyP4nbpwwA0b60MOPDKZVnssB0Q==",
|
||||
"version": "0.5.23",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.23.tgz",
|
||||
"integrity": "sha512-is/UJHgS3lvEuXyb+C/OPeIP5CKp+M6SQt1l/WFJr1Oj+KYYHGU8Ztlh/qOmAWgONhg286N4/cLNzTtAAh4YnA==",
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"openai": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types/node_modules/openai": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-5.3.0.tgz",
|
||||
"integrity": "sha512-VIKmoF7y4oJCDOwP/oHXGzM69+x0dpGFmN9QmYO+uPbLFOmmnwO+x1GbsgUtI+6oraxomGZ566Y421oYVu191w==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"openai": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types/node_modules/ws": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
@@ -134,19 +182,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
|
||||
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
|
||||
"version": "24.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
|
||||
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -684,9 +732,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -698,9 +746,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/client",
|
||||
"version": "1.3.13",
|
||||
"version": "1.3.17",
|
||||
"description": "",
|
||||
"main": "dist/packages/client/src/index.js",
|
||||
"scripts": {
|
||||
@@ -13,13 +13,13 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/ws": "^8.18.0",
|
||||
"@types/node": "^24.0.10",
|
||||
"@types/ws": "^8.18.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.2"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@scrypted/types": "^0.5.12"
|
||||
"@scrypted/types": "^0.5.23"
|
||||
},
|
||||
"dependencies": {
|
||||
"engine.io-client": "^6.6.3",
|
||||
|
||||
947
packages/deferred/package-lock.json
generated
947
packages/deferred/package-lock.json
generated
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@scrypted/rpc",
|
||||
"version": "0.0.5",
|
||||
"name": "@scrypted/deferred",
|
||||
"version": "0.0.8",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/rpc",
|
||||
"version": "0.0.5",
|
||||
"name": "@scrypted/deferred",
|
||||
"version": "0.0.8",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"rimraf": "^4.1.1",
|
||||
"typescript": "^4.7.4"
|
||||
"@types/node": "^24.0.10",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -43,19 +43,141 @@
|
||||
"../sdk/types": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.1.tgz",
|
||||
"integrity": "sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==",
|
||||
"node_modules/@isaacs/balanced-match": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rimraf": "dist/cjs/src/bin.js"
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/brace-expansion": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
|
||||
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -64,38 +186,793 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"node_modules/glob": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
|
||||
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.3.1",
|
||||
"jackspeak": "^4.1.1",
|
||||
"minimatch": "^10.0.3",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
|
||||
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
|
||||
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
|
||||
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@isaacs/brace-expansion": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
|
||||
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
|
||||
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^11.0.0",
|
||||
"package-json-from-dist": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
|
||||
"@isaacs/balanced-match": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@isaacs/brace-expansion": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "24.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
|
||||
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true,
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
|
||||
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"foreground-child": "^3.3.1",
|
||||
"jackspeak": "^4.1.1",
|
||||
"minimatch": "^10.0.3",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"jackspeak": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
|
||||
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
|
||||
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
|
||||
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@isaacs/brace-expansion": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"path-scurry": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
|
||||
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.1.tgz",
|
||||
"integrity": "sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
|
||||
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^11.0.0",
|
||||
"package-json-from-dist": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"string-width-cjs": {
|
||||
"version": "npm:string-width@4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi-cjs": {
|
||||
"version": "npm:strip-ansi@6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||
"dev": true
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"wrap-ansi-cjs": {
|
||||
"version": "npm:wrap-ansi@7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/deferred",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.8",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
@@ -12,8 +12,8 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"rimraf": "^4.1.1",
|
||||
"typescript": "^4.7.4"
|
||||
"@types/node": "^24.0.10",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
4
packages/rpc/package-lock.json
generated
4
packages/rpc/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/rpc",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/rpc",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/rpc",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
||||
4
plugins/amcrest/package-lock.json
generated
4
plugins/amcrest/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.165",
|
||||
"version": "0.0.166",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.165",
|
||||
"version": "0.0.166",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.165",
|
||||
"version": "0.0.166",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { AuthFetchCredentialState, HttpFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { AuthFetchCredentialState, authHttpFetch, HttpFetchOptions } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { readLine } from '@scrypted/common/src/read-stream';
|
||||
import { parseHeaders, readBody } from '@scrypted/common/src/rtsp-server';
|
||||
import { MediaStreamConfiguration, Point } from '@scrypted/sdk';
|
||||
import contentType from 'content-type';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { EventEmitter, Readable } from 'stream';
|
||||
import { createRtspMediaStreamOptions, Destroyable, UrlMediaStreamOptions } from '../../rtsp/src/rtsp';
|
||||
import { getDeviceInfo } from './probe';
|
||||
import { MediaStreamConfiguration, MediaStreamOptions, Point } from '@scrypted/sdk';
|
||||
|
||||
export interface AmcrestObjectDetails {
|
||||
Action: string;
|
||||
@@ -81,8 +81,11 @@ async function readAmcrestMessage(client: Readable): Promise<string[]> {
|
||||
}
|
||||
}
|
||||
|
||||
function findValue(blob: string, prefix: string, key: string) {
|
||||
const lines = blob.split('\n');
|
||||
function getLines(blob: string) {
|
||||
return blob.split(/\r?\n/).filter(line => line);
|
||||
}
|
||||
|
||||
function findValue(lines: string[], prefix: string, key: string) {
|
||||
const value = lines.find(line => line.startsWith(`${prefix}.${key}`));
|
||||
if (!value)
|
||||
return;
|
||||
@@ -124,7 +127,7 @@ const amcrestResolutions = {
|
||||
"720P": [1280, 720],
|
||||
"D1": [704, 480],
|
||||
"HD1": [352, 480],
|
||||
"BCIF": [704, 240],
|
||||
"BCIF": [528, 240],
|
||||
"2CIF": [704, 240],
|
||||
"CIF": [352, 240],
|
||||
"QCIF": [176, 120],
|
||||
@@ -133,7 +136,21 @@ const amcrestResolutions = {
|
||||
"QVGA": [320, 240]
|
||||
};
|
||||
|
||||
function fromAmcrestResolution(resolution: string) {
|
||||
const palAmcrestResolutions = {
|
||||
"D1": [704, 576],
|
||||
"HD1": [352, 576],
|
||||
"BCIF": [528, 288],
|
||||
"2CIF": [704, 288],
|
||||
"CIF": [352, 288],
|
||||
"QCIF": [176, 144],
|
||||
};
|
||||
|
||||
function fromAmcrestResolution(resolution: string, videoStandard: string) {
|
||||
if (videoStandard === 'PAL') {
|
||||
const named = palAmcrestResolutions[resolution];
|
||||
if (named)
|
||||
return named;
|
||||
}
|
||||
const named = amcrestResolutions[resolution];
|
||||
if (named)
|
||||
return named;
|
||||
@@ -438,6 +455,12 @@ export class AmcrestCameraClient {
|
||||
|
||||
this.console.log(capsResponse.body);
|
||||
|
||||
const videoStandardResponse = await this.request({
|
||||
url: `http://${this.ip}/cgi-bin/configManager.cgi?action=getConfig&name=VideoStandard`,
|
||||
responseType: 'text',
|
||||
});
|
||||
this.console.log(videoStandardResponse.body);
|
||||
|
||||
const formatNumber = Math.max(0, parseInt(options.id?.substring('channel'.length)) - 1);
|
||||
const format = options.id === 'channel0' ? 'MainFormat' : 'ExtraFormat';
|
||||
const encode = `Encode[${cameraNumber - 1}].${format}[${formatNumber}]`;
|
||||
@@ -493,17 +516,19 @@ export class AmcrestCameraClient {
|
||||
|
||||
const caps = `caps[${cameraNumber - 1}].${format}[${formatNumber}]`;
|
||||
const singleCaps = `caps.${format}[${formatNumber}]`;
|
||||
const capsLines = getLines(capsResponse.body);
|
||||
const videoStandard = findValue(getLines(videoStandardResponse.body), 'table', 'VideoStandard');
|
||||
|
||||
const findCaps = (key: string) => {
|
||||
const found = findValue(capsResponse.body, caps, key);
|
||||
const found = findValue(capsLines, caps, key);
|
||||
if (found)
|
||||
return found;
|
||||
// ad410 doesnt return a camera number if accessed directly
|
||||
if (cameraNumber - 1 === 0)
|
||||
return findValue(capsResponse.body, singleCaps, key);
|
||||
return findValue(capsLines, singleCaps, key);
|
||||
}
|
||||
|
||||
const resolutions = findCaps('Video.ResolutionTypes').split(',').map(fromAmcrestResolution);
|
||||
const resolutions = findCaps('Video.ResolutionTypes').split(',').map(r => fromAmcrestResolution(r, videoStandard));
|
||||
const bitrates = findCaps('Video.BitRateOptions').split(',').map(s => parseInt(s) * 1000);
|
||||
const fpsMax = parseInt(findCaps('Video.FPSMax'));
|
||||
const vso: MediaStreamConfiguration = {
|
||||
@@ -533,6 +558,7 @@ export class AmcrestCameraClient {
|
||||
responseType: 'text',
|
||||
});
|
||||
this.console.log(encodeResponse.body);
|
||||
const encodeLines = getLines(encodeResponse.body);
|
||||
|
||||
for (let i = 0; i < vsos.length; i++) {
|
||||
const vso = vsos[i];
|
||||
@@ -544,27 +570,27 @@ export class AmcrestCameraClient {
|
||||
encName = `table.Encode[${cameraNumber - 1}].ExtraFormat[${i - 1}]`;
|
||||
}
|
||||
|
||||
const videoCodec = fromAmcrestVideoCodec(findValue(encodeResponse.body, encName, 'Video.Compression'));
|
||||
const audioCodec = fromAmcrestAudioCodec(findValue(encodeResponse.body, encName, 'Audio.Compression'));
|
||||
const videoCodec = fromAmcrestVideoCodec(findValue(encodeLines, encName, 'Video.Compression'));
|
||||
const audioCodec = fromAmcrestAudioCodec(findValue(encodeLines, encName, 'Audio.Compression'));
|
||||
|
||||
if (vso.audio)
|
||||
vso.audio.codec = audioCodec;
|
||||
vso.video.codec = videoCodec;
|
||||
|
||||
const width = findValue(encodeResponse.body, encName, 'Video.Width');
|
||||
const height = findValue(encodeResponse.body, encName, 'Video.Height');
|
||||
const width = findValue(encodeLines, encName, 'Video.Width');
|
||||
const height = findValue(encodeLines, encName, 'Video.Height');
|
||||
if (width && height) {
|
||||
vso.video.width = parseInt(width);
|
||||
vso.video.height = parseInt(height);
|
||||
}
|
||||
|
||||
const videoEnable = findValue(encodeResponse.body, encName, 'VideoEnable');
|
||||
const videoEnable = findValue(encodeLines, encName, 'VideoEnable');
|
||||
if (videoEnable?.trim() === 'false') {
|
||||
this.console.warn('Video stream is disabled and should likely be enabled:', encName);
|
||||
continue;
|
||||
}
|
||||
|
||||
const encodeOptions = findValue(encodeResponse.body, encName, 'Video.BitRate');
|
||||
const encodeOptions = findValue(encodeLines, encName, 'Video.BitRate');
|
||||
if (!encodeOptions)
|
||||
continue;
|
||||
|
||||
|
||||
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.120",
|
||||
"version": "0.3.129",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.120",
|
||||
"version": "0.3.129",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.120",
|
||||
"version": "0.3.129",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DeviceState, MixinProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
|
||||
import { typeToIcon } from "../../../../manage.scrypted.app/src/device-icons";
|
||||
import { typeToIcon } from "../../../../manage.scrypted.app/src/util/device-icons";
|
||||
|
||||
export class LauncherMixin extends ScryptedDeviceBase implements MixinProvider, Readme {
|
||||
async getReadmeMarkdown(): Promise<string> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { readFileAsString, tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
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';
|
||||
@@ -8,6 +9,7 @@ import yaml from 'yaml';
|
||||
import { getUsableNetworkAddresses } from '../../../server/src/ip';
|
||||
import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
|
||||
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
|
||||
import { ClusterCore, ClusterCoreNativeId } from './cluster';
|
||||
import { LauncherMixin } from './launcher-mixin';
|
||||
import { MediaCore } from './media-core';
|
||||
import { checkLegacyLxc, checkLxc } from './platform/lxc';
|
||||
@@ -15,7 +17,6 @@ import { ConsoleServiceNativeId, PluginSocketService, ReplServiceNativeId } from
|
||||
import { ScriptCore, ScriptCoreNativeId, newScript } from './script-core';
|
||||
import { TerminalService, TerminalServiceNativeId, newTerminalService } from './terminal-service';
|
||||
import { UsersCore, UsersNativeId } from './user';
|
||||
import { ClusterCore, ClusterCoreNativeId } from './cluster';
|
||||
|
||||
const { deviceManager, endpointManager } = sdk;
|
||||
|
||||
@@ -210,6 +211,32 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
|
||||
},
|
||||
);
|
||||
})();
|
||||
|
||||
// check on workers once an hour.
|
||||
this.updateWorkers();
|
||||
setInterval(() => this.updateWorkers(), 1000 * 60 * 60);
|
||||
}
|
||||
|
||||
async updateWorkers() {
|
||||
const workers = await sdk.clusterManager?.getClusterWorkers();
|
||||
if (!workers)
|
||||
return;
|
||||
for (const [id, worker] of Object.entries(workers)) {
|
||||
const forked = sdk.fork<ReturnType<typeof fork>>({
|
||||
clusterWorkerId: id,
|
||||
runtime: 'node',
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const result = await forked.result;
|
||||
result.checkLxc();
|
||||
}
|
||||
catch (e) {
|
||||
forked.worker.terminate();
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
@@ -332,5 +359,15 @@ export async function fork() {
|
||||
tsCompile,
|
||||
newScript,
|
||||
newTerminalService,
|
||||
checkLxc: async () => {
|
||||
try {
|
||||
// console.warn('Checking for LXC installation...');
|
||||
await checkLxc();
|
||||
}
|
||||
finally {
|
||||
await sleep(1000);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ export async function checkLxc() {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.warn('lxc needs updating', sdk.clusterManager.getClusterWorkerId());
|
||||
// console.warn(foundDockerComposeSh);
|
||||
await fs.promises.copyFile(LXC_DOCKER_COMPOSE_SH_PATH, DOCKER_COMPOSE_SH_PATH);
|
||||
await fs.promises.chmod(DOCKER_COMPOSE_SH_PATH, 0o755);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedUser, ScryptedUserAccessControl, Setting, Settings, SettingValue } from "@scrypted/sdk";
|
||||
import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceManifest, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedUser, ScryptedUserAccessControl, Setting, Settings, SettingValue } from "@scrypted/sdk";
|
||||
import { addAccessControlsForInterface } from "@scrypted/sdk/acl";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
export const UsersNativeId = 'users';
|
||||
@@ -132,7 +132,13 @@ export class UsersCore extends ScryptedDeviceBase implements Readme, DeviceProvi
|
||||
deviceCreator: 'Scrypted User',
|
||||
};
|
||||
|
||||
this.syncUsers();
|
||||
this.syncUsers()
|
||||
.then(length => {
|
||||
if (!length) {
|
||||
this.console.log('no users found, looping for first user');
|
||||
setInterval(() => this.syncUsers(), 60 * 1000);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getDevice(nativeId: string): Promise<any> {
|
||||
@@ -192,7 +198,7 @@ export class UsersCore extends ScryptedDeviceBase implements Readme, DeviceProvi
|
||||
async syncUsers() {
|
||||
const usersService = await sdk.systemManager.getComponent('users');
|
||||
const users: DBUser[] = await usersService.getAllUsers();
|
||||
await sdk.deviceManager.onDevicesChanged({
|
||||
const manifest: DeviceManifest = {
|
||||
providerNativeId: this.nativeId,
|
||||
devices: users.map(user => ({
|
||||
name: user.username,
|
||||
@@ -203,6 +209,16 @@ export class UsersCore extends ScryptedDeviceBase implements Readme, DeviceProvi
|
||||
],
|
||||
type: ScryptedDeviceType.Person,
|
||||
})),
|
||||
})
|
||||
};
|
||||
const nativeIds = new Set(manifest.devices.map(d => d.nativeId));
|
||||
for (const nativeId of sdk.deviceManager.getNativeIds()) {
|
||||
nativeIds.delete(nativeId);
|
||||
}
|
||||
if (nativeIds.size) {
|
||||
// add any missing users.
|
||||
await sdk.deviceManager.onDevicesChanged(manifest);
|
||||
}
|
||||
|
||||
return manifest.devices.length;
|
||||
}
|
||||
}
|
||||
|
||||
58
plugins/coreml/package-lock.json
generated
58
plugins/coreml/package-lock.json
generated
@@ -1,34 +1,42 @@
|
||||
{
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.77",
|
||||
"version": "0.1.83",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.77",
|
||||
"version": "0.1.83",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.77",
|
||||
"version": "0.5.22",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.5",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.2.1",
|
||||
"axios": "^1.10.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"openai": "^5.3.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.43.0",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.95.0",
|
||||
"ts-loader": "^9.5.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.3",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
@@ -41,11 +49,9 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.10"
|
||||
"typedoc": "^0.28.5"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
@@ -60,23 +66,29 @@
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.5",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"@types/node": "^24.0.1",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"babel-loader": "^9.2.1",
|
||||
"axios": "^1.10.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"openai": "^5.3.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"stringify-object": "^3.3.0",
|
||||
"rollup": "^4.43.0",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.10",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.95.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typedoc": "^0.28.5",
|
||||
"typescript": "^5.8.3",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
"runtime": "python",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"ScryptedSystemDevice",
|
||||
"DeviceCreator",
|
||||
"Settings",
|
||||
"DeviceProvider",
|
||||
"ClusterForkInterface",
|
||||
@@ -48,5 +50,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.77"
|
||||
"version": "0.1.83"
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ from scrypted_sdk import Setting, SettingValue
|
||||
|
||||
from common import yolo
|
||||
from coreml.face_recognition import CoreMLFaceRecognition
|
||||
from coreml.custom_detection import CoreMLCustomDetection
|
||||
from coreml.clip_embedding import CoreMLClipEmbedding
|
||||
|
||||
try:
|
||||
from coreml.text_recognition import CoreMLTextRecognition
|
||||
@@ -77,6 +79,8 @@ class CoreMLPlugin(
|
||||
def __init__(self, nativeId: str | None = None, forked: bool = False):
|
||||
super().__init__(nativeId=nativeId, forked=forked)
|
||||
|
||||
self.custom_models = {}
|
||||
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
if model == "Default" or model not in availableModels:
|
||||
if model != "Default":
|
||||
@@ -143,13 +147,14 @@ class CoreMLPlugin(
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
self.clipDevice = None
|
||||
|
||||
if not self.forked:
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
devices = [
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
@@ -159,10 +164,10 @@ class CoreMLPlugin(
|
||||
],
|
||||
"name": "CoreML Face Recognition",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
if CoreMLTextRecognition:
|
||||
devices.append(
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
@@ -174,9 +179,17 @@ class CoreMLPlugin(
|
||||
},
|
||||
)
|
||||
|
||||
await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"devices": devices,
|
||||
"nativeId": "clipembedding",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
scrypted_sdk.ScryptedInterface.TextEmbedding.value,
|
||||
scrypted_sdk.ScryptedInterface.ImageEmbedding.value,
|
||||
],
|
||||
"name": "CoreML CLIP Embedding",
|
||||
}
|
||||
)
|
||||
except:
|
||||
@@ -186,10 +199,19 @@ class CoreMLPlugin(
|
||||
if nativeId == "facerecognition":
|
||||
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(self, nativeId)
|
||||
return self.faceDevice
|
||||
if nativeId == "textrecognition":
|
||||
elif nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or CoreMLTextRecognition(self, nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
elif nativeId == "clipembedding":
|
||||
self.clipDevice = self.clipDevice or CoreMLClipEmbedding(self, nativeId)
|
||||
return self.clipDevice
|
||||
custom_model = self.custom_models.get(nativeId, None)
|
||||
if custom_model:
|
||||
return custom_model
|
||||
custom_model = CoreMLCustomDetection(self, nativeId)
|
||||
self.custom_models[nativeId] = custom_model
|
||||
await custom_model.reportDevice(nativeId, custom_model.providedName)
|
||||
return custom_model
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
|
||||
85
plugins/coreml/src/coreml/clip_embedding.py
Normal file
85
plugins/coreml/src/coreml/clip_embedding.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import coremltools as ct
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from scrypted_sdk import ObjectsDetected
|
||||
|
||||
from predict.clip import ClipEmbedding
|
||||
|
||||
|
||||
class CoreMLClipEmbedding(ClipEmbedding):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
self.predictExecutor = concurrent.futures.ThreadPoolExecutor(1, "predict-clip")
|
||||
|
||||
def getFiles(self):
|
||||
return [
|
||||
"text.mlpackage/Manifest.json",
|
||||
"text.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
|
||||
"text.mlpackage/Data/com.apple.CoreML/model.mlmodel",
|
||||
|
||||
"vision.mlpackage/Manifest.json",
|
||||
"vision.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
|
||||
"vision.mlpackage/Data/com.apple.CoreML/model.mlmodel",
|
||||
]
|
||||
|
||||
def loadModel(self, files):
|
||||
# find the xml file in the files list
|
||||
text_manifest = [f for f in files if f.lower().endswith('text.mlpackage/manifest.json')]
|
||||
if not text_manifest:
|
||||
raise ValueError("No XML model file found in the provided files list")
|
||||
text_manifest = text_manifest[0]
|
||||
|
||||
vision_manifest = [f for f in files if f.lower().endswith('vision.mlpackage/manifest.json')]
|
||||
if not vision_manifest:
|
||||
raise ValueError("No XML model file found in the provided files list")
|
||||
vision_manifest = vision_manifest[0]
|
||||
|
||||
|
||||
textModel = ct.models.MLModel(os.path.dirname(text_manifest))
|
||||
visionModel = ct.models.MLModel(os.path.dirname(vision_manifest))
|
||||
|
||||
return textModel, visionModel
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
def predict():
|
||||
inputs = self.processor(images=input, return_tensors="np", padding="max_length", truncation=True)
|
||||
_, vision_model = self.model
|
||||
vision_predictions = vision_model.predict({'x': inputs['pixel_values']})
|
||||
image_embeds = vision_predictions['var_877']
|
||||
# this is a hack to utilize the existing image massaging infrastructure
|
||||
embedding = bytearray(image_embeds.astype(np.float32).tobytes())
|
||||
ret: ObjectsDetected = {
|
||||
"detections": [
|
||||
{
|
||||
"embedding": embedding,
|
||||
}
|
||||
],
|
||||
"inputDimensions": src_size
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
ret = await asyncio.get_event_loop().run_in_executor(
|
||||
self.predictExecutor, lambda: predict()
|
||||
)
|
||||
return ret
|
||||
|
||||
async def getTextEmbedding(self, input):
|
||||
def predict():
|
||||
inputs = self.processor(text=input, return_tensors="np", padding="max_length", truncation=True)
|
||||
text_model, _ = self.model
|
||||
text_predictions = text_model.predict({'input_ids_1': inputs['input_ids'].astype(np.float32), 'attention_mask_1': inputs['attention_mask'].astype(np.float32)})
|
||||
text_embeds = text_predictions['var_1050']
|
||||
return bytearray(text_embeds.astype(np.float32).tobytes())
|
||||
|
||||
ret = await asyncio.get_event_loop().run_in_executor(
|
||||
self.predictExecutor, lambda: predict()
|
||||
)
|
||||
return ret
|
||||
60
plugins/coreml/src/coreml/custom_detection.py
Normal file
60
plugins/coreml/src/coreml/custom_detection.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import os
|
||||
|
||||
import coremltools as ct
|
||||
import numpy as np
|
||||
import scrypted_sdk
|
||||
from PIL import Image
|
||||
|
||||
from predict.custom_detect import CustomDetection
|
||||
|
||||
|
||||
class CoreMLCustomDetection(CustomDetection):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
self.prefer_relu = True
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-custom")
|
||||
|
||||
def loadModel(self, files: list[str]):
|
||||
# find the xml file in the files list
|
||||
manifest_files = [f for f in files if f.lower().endswith('manifest.json')]
|
||||
if not manifest_files:
|
||||
raise ValueError("No Manifest.json file found in the provided files list")
|
||||
manifest_file = manifest_files[0]
|
||||
modelFile = os.path.dirname(manifest_file)
|
||||
|
||||
model = ct.models.MLModel(modelFile)
|
||||
inputName = model.get_spec().description.input[0].name
|
||||
return model, inputName
|
||||
|
||||
async def predictModel(self, input: Image.Image) -> scrypted_sdk.ObjectsDetected:
|
||||
model, inputName = self.model
|
||||
def predict():
|
||||
if self.model_config.get("mean", None) and self.model_config.get("std", None):
|
||||
im = np.array(input)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
|
||||
mean = np.array(self.model_config.get("mean", None), dtype=np.float32)
|
||||
std = np.array(self.model_config.get("std", None), dtype=np.float32)
|
||||
im = (im - mean) / std
|
||||
|
||||
# Convert HWC to CHW
|
||||
im = im.transpose(2, 0, 1) # Channels first
|
||||
im = im.astype(np.float32)
|
||||
im = np.ascontiguousarray(im)
|
||||
im = np.expand_dims(im, axis=0)
|
||||
|
||||
out_dict = model.predict({inputName: im})
|
||||
else:
|
||||
out_dict = model.predict({inputName: input})
|
||||
|
||||
results = list(out_dict.values())[0][0]
|
||||
return results
|
||||
|
||||
results = await asyncio.get_event_loop().run_in_executor(
|
||||
self.detectExecutor, lambda: predict()
|
||||
)
|
||||
return results
|
||||
@@ -25,9 +25,6 @@ def cosine_similarity(vector_a, vector_b):
|
||||
similarity = dot_product / (norm_a * norm_b)
|
||||
return similarity
|
||||
|
||||
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(8, "Vision-Predict")
|
||||
|
||||
class CoreMLFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin, nativeId)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
coremltools==8.0
|
||||
Pillow==10.3.0
|
||||
opencv-python-headless==4.10.0.84
|
||||
|
||||
transformers==4.52.4
|
||||
|
||||
@@ -522,32 +522,74 @@ export class HikvisionCameraAPI implements HikvisionAPI {
|
||||
|
||||
async setSupplementLight(params: { on?: boolean, brightness?: number, mode?: 'auto' | 'manual' }): Promise<void> {
|
||||
const { json } = await this.getSupplementLight();
|
||||
|
||||
if (json.ResponseStatus) {
|
||||
throw new Error("Supplemental light is not supported on this device.");
|
||||
}
|
||||
|
||||
const supp: any = json.SupplementLight;
|
||||
if (!supp) {
|
||||
throw new Error("Supplemental light configuration not available.");
|
||||
}
|
||||
|
||||
if (supp.supplementLightMode && supp.supplementLightMode.opt) {
|
||||
const availableModes = supp.supplementLightMode.opt.split(',').map(s => s.trim());
|
||||
const selectedMode = params.on
|
||||
? (availableModes.find(mode => mode.toLowerCase() !== 'close') || 'close')
|
||||
: 'close';
|
||||
supp.supplementLightMode = [selectedMode];
|
||||
const getCurrentValue = (obj: any) => Array.isArray(obj) ? obj[0] : obj;
|
||||
const setValue = (t: any, k: string, v: string) => {
|
||||
t[k] = Array.isArray(t[k]) ? [v] : v;
|
||||
};
|
||||
|
||||
const setBrightnessForMode = (level: number, mode: string) => {
|
||||
const v = level.toString();
|
||||
const map: Record<string, Array<{ obj: any; key: string }>> = {
|
||||
colorVuWhiteLight: [
|
||||
{ obj: supp, key: 'whiteLightBrightness' },
|
||||
{ obj: supp.colorVuWhiteLightModeCfg, key: 'whiteLightbrightLimit' }
|
||||
],
|
||||
irLight: [
|
||||
{ obj: supp, key: 'irLightBrightness' },
|
||||
{ obj: supp.IrLightModeCfg, key: 'irLightbrightLimit' }
|
||||
],
|
||||
eventIntelligence: [
|
||||
{ obj: supp.EventIntelligenceModeCfg, key: 'whiteLightBrightness' },
|
||||
{ obj: supp.EventIntelligenceModeCfg, key: 'irLightBrightness' }
|
||||
]
|
||||
};
|
||||
(map[mode] || []).forEach(({ obj, key }) => {
|
||||
if (obj && obj[key] !== undefined) setValue(obj, key, v);
|
||||
});
|
||||
};
|
||||
|
||||
const setModeConfigs = (m: 'auto' | 'manual') => {
|
||||
if (getCurrentValue(supp.supplementLightMode) === 'eventIntelligence' && supp.EventIntelligenceModeCfg) {
|
||||
setValue(supp.EventIntelligenceModeCfg, 'brightnessRegulatMode', m);
|
||||
} else if (supp.mixedLightBrightnessRegulatMode !== undefined) {
|
||||
setValue(supp, 'mixedLightBrightnessRegulatMode', m);
|
||||
} else if (supp.isAutoModeBrightnessCfg !== undefined) {
|
||||
setValue(supp, 'isAutoModeBrightnessCfg', m === 'auto' ? 'true' : 'false');
|
||||
}
|
||||
};
|
||||
|
||||
if (params.on !== undefined && supp.supplementLightMode) {
|
||||
const opts = supp.supplementLightMode.opt?.split(',').map((s: string) => s.trim()) || [];
|
||||
this.console.log('[API] Available supplemental light modes:', opts);
|
||||
if (params.on) {
|
||||
const preferred = ['colorVuWhiteLight', 'eventIntelligence', 'irLight'];
|
||||
const sel = preferred.find(m => opts.includes(m));
|
||||
if (!sel) {
|
||||
throw new Error(`Cannot turn on: no supported mode. Available: ${opts.join(', ')}`);
|
||||
}
|
||||
setValue(supp, 'supplementLightMode', sel);
|
||||
} else {
|
||||
setValue(supp, 'supplementLightMode', 'close');
|
||||
}
|
||||
}
|
||||
|
||||
if (params.mode) {
|
||||
supp.mixedLightBrightnessRegulatMode = [params.mode];
|
||||
} else if (params.on !== undefined) {
|
||||
supp.mixedLightBrightnessRegulatMode = [params.on ? "manual" : "auto"];
|
||||
setModeConfigs(params.mode);
|
||||
}
|
||||
|
||||
if (params.brightness !== undefined) {
|
||||
let brightness = Math.max(0, Math.min(100, params.brightness));
|
||||
supp.whiteLightBrightness = [brightness.toString()];
|
||||
const lvl = Math.min(100, Math.max(0, params.brightness));
|
||||
const mode = getCurrentValue(supp.supplementLightMode);
|
||||
if (mode !== 'close') {
|
||||
setBrightnessForMode(lvl, mode);
|
||||
} else {
|
||||
this.console.warn('[API] Brightness change ignored: light is off');
|
||||
}
|
||||
}
|
||||
|
||||
const builder = new xml2js.Builder({
|
||||
@@ -555,7 +597,7 @@ export class HikvisionCameraAPI implements HikvisionAPI {
|
||||
renderOpts: { pretty: false },
|
||||
});
|
||||
const newXml = builder.buildObject({ SupplementLight: supp });
|
||||
|
||||
|
||||
await this.request({
|
||||
method: 'PUT',
|
||||
url: `http://${this.ip}/ISAPI/Image/channels/1/supplementLight`,
|
||||
|
||||
48
plugins/ncnn/package-lock.json
generated
48
plugins/ncnn/package-lock.json
generated
@@ -1,34 +1,41 @@
|
||||
{
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.77",
|
||||
"name": "@scrypted/ncnn",
|
||||
"version": "0.1.88",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.77",
|
||||
"name": "@scrypted/ncnn",
|
||||
"version": "0.1.88",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.77",
|
||||
"version": "0.5.12",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-typescript": "^12.1.1",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"axios": "^1.7.8",
|
||||
"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",
|
||||
"rollup": "^4.27.4",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.95.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.6.3",
|
||||
"webpack": "^5.96.1",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
@@ -41,11 +48,9 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"@types/node": "^22.10.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.10"
|
||||
"typedoc": "^0.26.11"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
@@ -61,22 +66,27 @@
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@types/node": "^22.8.1",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-typescript": "^12.1.1",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"@types/node": "^22.10.1",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"axios": "^1.7.8",
|
||||
"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",
|
||||
"rollup": "^4.27.4",
|
||||
"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",
|
||||
"tslib": "^2.8.1",
|
||||
"typedoc": "^0.26.11",
|
||||
"typescript": "^5.6.3",
|
||||
"webpack": "^5.96.1",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,16 +33,22 @@
|
||||
"runtime": "python",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"ScryptedSystemDevice",
|
||||
"DeviceCreator",
|
||||
"Settings",
|
||||
"DeviceProvider",
|
||||
"ClusterForkInterface",
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview"
|
||||
]
|
||||
|
||||
],
|
||||
"labels": {
|
||||
"require": [
|
||||
"@scrypted/ncnn"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.77"
|
||||
"version": "0.1.88"
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ from __future__ import annotations
|
||||
import ast
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import traceback
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
@@ -17,12 +15,13 @@ from scrypted_sdk import Setting, SettingValue
|
||||
import ncnn
|
||||
from common import yolo
|
||||
|
||||
from .custom_detection import NCNNCustomDetection
|
||||
try:
|
||||
from ncnn.face_recognition import NCNNFaceRecognition
|
||||
from nc.face_recognition import NCNNFaceRecognition
|
||||
except:
|
||||
NCNNFaceRecognition = None
|
||||
try:
|
||||
from ncnn.text_recognition import NCNNTextRecognition
|
||||
from nc.text_recognition import NCNNTextRecognition
|
||||
except:
|
||||
NCNNTextRecognition = None
|
||||
from predict import Prediction, PredictPlugin
|
||||
@@ -33,18 +32,14 @@ prepareExecutor = concurrent.futures.ThreadPoolExecutor(1, "NCNN-Prepare")
|
||||
|
||||
availableModels = [
|
||||
"Default",
|
||||
"scrypted_yolov10m_320",
|
||||
"scrypted_yolov10n_320",
|
||||
"scrypted_yolo_nas_s_320",
|
||||
"scrypted_yolov9e_320",
|
||||
"scrypted_yolov9c_relu_320",
|
||||
"scrypted_yolov9m_relu_320",
|
||||
"scrypted_yolov9s_relu_320",
|
||||
"scrypted_yolov9t_relu_320",
|
||||
"scrypted_yolov9c_320",
|
||||
"scrypted_yolov9m_320",
|
||||
"scrypted_yolov9s_320",
|
||||
"scrypted_yolov9t_320",
|
||||
"scrypted_yolov6n_320",
|
||||
"scrypted_yolov6s_320",
|
||||
"scrypted_yolov8n_320",
|
||||
"ssdlite_mobilenet_v2",
|
||||
"yolov4-tiny",
|
||||
]
|
||||
|
||||
|
||||
@@ -84,6 +79,8 @@ class NCNNPlugin(
|
||||
def __init__(self, nativeId: str | None = None, forked: bool = False):
|
||||
super().__init__(nativeId=nativeId, forked=forked)
|
||||
|
||||
self.custom_models = {}
|
||||
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
if model == "Default" or model not in availableModels:
|
||||
if model != "Default":
|
||||
@@ -106,7 +103,7 @@ class NCNNPlugin(
|
||||
}
|
||||
files = [
|
||||
f"{model}/best_converted.ncnn.bin",
|
||||
f"{model}//best_converted.ncnn.param",
|
||||
f"{model}/best_converted.ncnn.param",
|
||||
]
|
||||
|
||||
for f in files:
|
||||
@@ -123,14 +120,10 @@ class NCNNPlugin(
|
||||
|
||||
|
||||
self.net = ncnn.Net()
|
||||
# self.net.opt.use_vulkan_compute = True
|
||||
# self.net.opt.use_winograd_convolution = False
|
||||
# self.net.opt.use_sgemm_convolution = False
|
||||
self.net.opt.use_vulkan_compute = True
|
||||
# self.net.opt.use_fp16_packed = False
|
||||
# self.net.opt.use_fp16_storage = False
|
||||
# self.net.opt.use_fp16_arithmetic = False
|
||||
# self.net.opt.use_int8_storage = False
|
||||
# self.net.opt.use_int8_arithmetic = False
|
||||
|
||||
self.net.load_param(paramFile)
|
||||
self.net.load_model(binFile)
|
||||
@@ -153,55 +146,55 @@ class NCNNPlugin(
|
||||
# self.loop = asyncio.get_event_loop()
|
||||
# self.minThreshold = 0.2
|
||||
|
||||
# self.faceDevice = None
|
||||
# self.textDevice = None
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
|
||||
# if not self.forked:
|
||||
# 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:
|
||||
# devices = [
|
||||
# {
|
||||
# "nativeId": "facerecognition",
|
||||
# "type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
# "interfaces": [
|
||||
# scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
# scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
# ],
|
||||
# "name": "NCNN Face Recognition",
|
||||
# },
|
||||
# ]
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "NCNN Face Recognition",
|
||||
},
|
||||
)
|
||||
|
||||
# if NCNNTextRecognition:
|
||||
# devices.append(
|
||||
# {
|
||||
# "nativeId": "textrecognition",
|
||||
# "type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
# "interfaces": [
|
||||
# scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
# scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
# ],
|
||||
# "name": "NCNN Text Recognition",
|
||||
# },
|
||||
# )
|
||||
if NCNNTextRecognition:
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "NCNN Text Recognition",
|
||||
},
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
# await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
# {
|
||||
# "devices": devices,
|
||||
# }
|
||||
# )
|
||||
# except:
|
||||
# pass
|
||||
|
||||
# async def getDevice(self, nativeId: str) -> Any:
|
||||
# if nativeId == "facerecognition":
|
||||
# self.faceDevice = self.faceDevice or NCNNFaceRecognition(self, nativeId)
|
||||
# return self.faceDevice
|
||||
# if nativeId == "textrecognition":
|
||||
# self.textDevice = self.textDevice or NCNNTextRecognition(self, nativeId)
|
||||
# return self.textDevice
|
||||
# raise Exception("unknown device")
|
||||
async def getDevice(self, nativeId: str) -> Any:
|
||||
if nativeId == "facerecognition":
|
||||
self.faceDevice = self.faceDevice or NCNNFaceRecognition(self, nativeId)
|
||||
return self.faceDevice
|
||||
if nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or NCNNTextRecognition(self, nativeId)
|
||||
return self.textDevice
|
||||
custom_model = self.custom_models.get(nativeId, None)
|
||||
if custom_model:
|
||||
return custom_model
|
||||
custom_model = NCNNCustomDetection(self, nativeId)
|
||||
self.custom_models[nativeId] = custom_model
|
||||
await custom_model.reportDevice(nativeId, custom_model.providedName)
|
||||
return custom_model
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
@@ -239,6 +232,8 @@ class NCNNPlugin(
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
|
||||
im = im.reshape((1, 3, 320, 320)).squeeze(0)
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
|
||||
1
plugins/ncnn/src/nc/async_infer.py
Symbolic link
1
plugins/ncnn/src/nc/async_infer.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../openvino/src/ov/async_infer.py
|
||||
82
plugins/ncnn/src/nc/custom_detection.py
Normal file
82
plugins/ncnn/src/nc/custom_detection.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import os
|
||||
import ncnn
|
||||
|
||||
from predict.custom_detect import CustomDetection
|
||||
from scrypted_sdk import ObjectsDetected
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
class NCNNCustomDetection(CustomDetection):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
self.prefer_relu = True
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-custom")
|
||||
self.prepareExecutor = concurrent.futures.ThreadPoolExecutor(1, "prepare-custom")
|
||||
|
||||
def loadModel(self, files: list[str]):
|
||||
# find the xml file in the files list
|
||||
bin_files = [f for f in files if f.lower().endswith('.bin')]
|
||||
if not bin_files:
|
||||
raise ValueError("No bkin file found in the provided files list")
|
||||
bin_file = bin_files[0]
|
||||
param_files = [f for f in files if f.lower().endswith('.param')]
|
||||
if not param_files:
|
||||
raise ValueError("No param file found in the provided files list")
|
||||
param_file = param_files[0]
|
||||
|
||||
net = ncnn.Net()
|
||||
net.opt.use_vulkan_compute = True
|
||||
# net.opt.use_fp16_packed = False
|
||||
# net.opt.use_fp16_storage = False
|
||||
# net.opt.use_fp16_arithmetic = False
|
||||
|
||||
net.load_param(param_file)
|
||||
net.load_model(bin_file)
|
||||
|
||||
input_name = net.input_names()[0]
|
||||
|
||||
return net, input_name
|
||||
|
||||
async def predictModel(self, input: Image.Image) -> ObjectsDetected:
|
||||
def prepare():
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
|
||||
if self.model_config.get("mean", None) and self.model_config.get("std", None):
|
||||
mean = np.array(self.model_config["mean"])
|
||||
std = np.array(self.model_config["std"])
|
||||
mean = mean.reshape(1, -1, 1, 1)
|
||||
std = std.reshape(1, -1, 1, 1)
|
||||
im = (im - mean) / std
|
||||
im = im.astype(np.float32)
|
||||
|
||||
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
|
||||
im = im.squeeze(0)
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
net, input_name = self.model
|
||||
input_ncnn = ncnn.Mat(input_tensor)
|
||||
ex = net.create_extractor()
|
||||
ex.input(input_name, input_ncnn)
|
||||
|
||||
output_ncnn = ncnn.Mat()
|
||||
ex.extract("out0", output_ncnn)
|
||||
|
||||
output_tensors = np.array(output_ncnn)
|
||||
return output_tensors
|
||||
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
self.prepareExecutor, lambda: prepare()
|
||||
)
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
self.detectExecutor, lambda: predict(input_tensor)
|
||||
)
|
||||
111
plugins/ncnn/src/nc/face_recognition.py
Normal file
111
plugins/ncnn/src/nc/face_recognition.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
import ncnn
|
||||
from nc import async_infer
|
||||
from predict.face_recognize import FaceRecognizeDetection
|
||||
|
||||
faceDetectPrepare, faceDetectPredict = async_infer.create_executors("FaceDetect")
|
||||
faceRecognizePrepare, faceRecognizePredict = async_infer.create_executors(
|
||||
"FaceRecognize"
|
||||
)
|
||||
|
||||
|
||||
class NCNNFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
self.prefer_relu = True
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
scrypted_yolov9 = "scrypted_yolov9" in model
|
||||
ncnnmodel = "best_converted" if scrypted_yolov9 else model
|
||||
model_version = "v1"
|
||||
files = [
|
||||
f"{model}/{ncnnmodel}.ncnn.bin",
|
||||
f"{model}/{ncnnmodel}.ncnn.param",
|
||||
]
|
||||
|
||||
for f in files:
|
||||
p = self.downloadFile(
|
||||
f"https://github.com/koush/ncnn-models/raw/main/{f}",
|
||||
f"{model_version}/{f}",
|
||||
)
|
||||
if ".bin" in p:
|
||||
binFile = p
|
||||
if ".param" in p:
|
||||
paramFile = p
|
||||
|
||||
|
||||
net = ncnn.Net()
|
||||
net.opt.use_vulkan_compute = True
|
||||
# net.opt.use_fp16_packed = False
|
||||
# net.opt.use_fp16_storage = False
|
||||
# net.opt.use_fp16_arithmetic = False
|
||||
|
||||
net.load_param(paramFile)
|
||||
net.load_model(binFile)
|
||||
|
||||
input_name = net.input_names()[0]
|
||||
|
||||
return [net, input_name]
|
||||
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
def prepare():
|
||||
im = np.array(input)
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
|
||||
im = im.reshape((1, 3, 320, 320)).squeeze(0)
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
net, input_name = self.detectModel
|
||||
input_ncnn = ncnn.Mat(input_tensor)
|
||||
ex = net.create_extractor()
|
||||
ex.input(input_name, input_ncnn)
|
||||
|
||||
output_ncnn = ncnn.Mat()
|
||||
ex.extract("out0", output_ncnn)
|
||||
|
||||
output_tensors = np.array(output_ncnn)
|
||||
return output_tensors
|
||||
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
faceDetectPrepare, lambda: prepare()
|
||||
)
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
faceDetectPredict, lambda: predict(input_tensor)
|
||||
)
|
||||
|
||||
|
||||
async def predictFaceModel(self, input: np.ndarray):
|
||||
def prepare():
|
||||
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
|
||||
im = input.squeeze(0)
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
net, input_name = self.faceModel
|
||||
input_ncnn = ncnn.Mat(input_tensor)
|
||||
ex = net.create_extractor()
|
||||
ex.input(input_name, input_ncnn)
|
||||
|
||||
output_ncnn = ncnn.Mat()
|
||||
ex.extract("out0", output_ncnn)
|
||||
|
||||
output_tensors = np.array(output_ncnn)
|
||||
return output_tensors
|
||||
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
faceDetectPrepare, lambda: prepare()
|
||||
)
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
faceDetectPredict, lambda: predict(input_tensor)
|
||||
)
|
||||
105
plugins/ncnn/src/nc/text_recognition.py
Normal file
105
plugins/ncnn/src/nc/text_recognition.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ncnn
|
||||
from nc import async_infer
|
||||
from predict.text_recognize import TextRecognition
|
||||
|
||||
textDetectPrepare, textDetectPredict = async_infer.create_executors("TextDetect")
|
||||
textRecognizePrepare, textRecognizePredict = async_infer.create_executors(
|
||||
"TextRecognize"
|
||||
)
|
||||
|
||||
|
||||
class NCNNTextRecognition(TextRecognition):
|
||||
def downloadModel(self, model: str):
|
||||
model_version = "v1"
|
||||
files = [
|
||||
f"{model}/{model}.ncnn.bin",
|
||||
f"{model}/{model}.ncnn.param",
|
||||
]
|
||||
|
||||
for f in files:
|
||||
p = self.downloadFile(
|
||||
f"https://github.com/koush/ncnn-models/raw/main/{f}",
|
||||
f"{model_version}/{f}",
|
||||
)
|
||||
if ".bin" in p:
|
||||
binFile = p
|
||||
if ".param" in p:
|
||||
paramFile = p
|
||||
|
||||
|
||||
net = ncnn.Net()
|
||||
net.opt.use_vulkan_compute = True
|
||||
# net.opt.use_fp16_packed = False
|
||||
# net.opt.use_fp16_storage = False
|
||||
# net.opt.use_fp16_arithmetic = False
|
||||
|
||||
net.load_param(paramFile)
|
||||
net.load_model(binFile)
|
||||
|
||||
input_name = net.input_names()[0]
|
||||
|
||||
return [net, input_name]
|
||||
|
||||
|
||||
async def predictDetectModel(self, input: np.ndarray):
|
||||
def prepare():
|
||||
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
|
||||
im = input.squeeze(0)
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
net, input_name = self.detectModel
|
||||
input_ncnn = ncnn.Mat(input_tensor)
|
||||
ex = net.create_extractor()
|
||||
ex.input(input_name, input_ncnn)
|
||||
|
||||
output_ncnn = ncnn.Mat()
|
||||
ex.extract("out0", output_ncnn)
|
||||
|
||||
output_tensors = np.array(output_ncnn)
|
||||
output_tensors = output_tensors.transpose((1, 2, 0))
|
||||
# readd a batch dimension
|
||||
output_tensors = np.expand_dims(output_tensors, axis=0)
|
||||
return output_tensors
|
||||
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
textDetectPrepare, lambda: prepare()
|
||||
)
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
textDetectPredict, lambda: predict(input_tensor)
|
||||
)
|
||||
|
||||
async def predictTextModel(self, input: np.ndarray):
|
||||
def prepare():
|
||||
# no batch? https://github.com/Tencent/ncnn/issues/5990#issuecomment-2832927105
|
||||
im = input.squeeze(0)
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
net, input_name = self.textModel
|
||||
input_ncnn = ncnn.Mat(input_tensor)
|
||||
ex = net.create_extractor()
|
||||
ex.input(input_name, input_ncnn)
|
||||
|
||||
output_ncnn = ncnn.Mat()
|
||||
ex.extract("out0", output_ncnn)
|
||||
|
||||
output_tensors = np.array(output_ncnn)
|
||||
# readd a batch dimension
|
||||
output_tensors = np.expand_dims(output_tensors, axis=0)
|
||||
return output_tensors
|
||||
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
textRecognizePrepare, lambda: prepare()
|
||||
)
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
textRecognizePredict, lambda: predict(input_tensor)
|
||||
)
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.68",
|
||||
"version": "0.1.72",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.68",
|
||||
"version": "0.1.72",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.68",
|
||||
"version": "0.1.72",
|
||||
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -138,7 +138,6 @@ export class FFmpegVideoFrameGenerator extends ScryptedDeviceBase implements Vid
|
||||
|
||||
try {
|
||||
reader();
|
||||
const flush = async () => { };
|
||||
|
||||
while (!finished) {
|
||||
frameDeferred = new Deferred();
|
||||
@@ -151,9 +150,7 @@ export class FFmpegVideoFrameGenerator extends ScryptedDeviceBase implements Vid
|
||||
yield {
|
||||
__json_copy_serialize_children: true,
|
||||
timestamp: 0,
|
||||
queued: 0,
|
||||
image,
|
||||
flush,
|
||||
};
|
||||
}
|
||||
finally {
|
||||
|
||||
@@ -9,7 +9,8 @@ import { FFmpegVideoFrameGenerator } from './ffmpeg-videoframes';
|
||||
import { fixLegacyClipPath, normalizeBox, polygonContainsBoundingBox, polygonIntersectsBoundingBox } from './polygon';
|
||||
import { SMART_MOTIONSENSOR_PREFIX, SmartMotionSensor } from './smart-motionsensor';
|
||||
import { SMART_OCCUPANCYSENSOR_PREFIX, SmartOccupancySensor } from './smart-occupancy-sensor';
|
||||
import { getAllDevices, safeParseJson } from './util';
|
||||
import { safeParseJson } from '../../../common/src/json';
|
||||
import { getAllDevices } from '../../../common/src/devices';
|
||||
import { FFmpegAudioDetectionMixinProvider } from './ffmpeg-audiosensor';
|
||||
|
||||
|
||||
@@ -94,7 +95,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
onGet: async () => {
|
||||
const choices = [
|
||||
'Default',
|
||||
...getAllDevices().filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator)).map(d => d.name),
|
||||
...getAllDevices(sdk.systemManager).filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator)).map(d => d.name),
|
||||
];
|
||||
return {
|
||||
hide: this.model?.decoder,
|
||||
@@ -690,7 +691,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
if (frameGenerator === 'Default')
|
||||
frameGenerator = this.plugin.storageSettings.values.defaultDecoder || 'Default';
|
||||
|
||||
const pipelines = getAllDevices().filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator));
|
||||
const pipelines = getAllDevices(sdk.systemManager).filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator));
|
||||
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;
|
||||
@@ -1026,7 +1027,7 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
|
||||
onGet: async () => {
|
||||
const choices = [
|
||||
'Default',
|
||||
...getAllDevices().filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator)).map(d => d.name),
|
||||
...getAllDevices(sdk.systemManager).filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator)).map(d => d.name),
|
||||
];
|
||||
return {
|
||||
choices,
|
||||
|
||||
@@ -85,7 +85,8 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
|
||||
|
||||
this.storageSettings.settings.detections.onGet = async () => {
|
||||
const objectDetector: ObjectDetector = this.storageSettings.values.objectDetector;
|
||||
const choices = (await objectDetector?.getObjectTypes?.())?.classes || [];
|
||||
const classes = (await objectDetector?.getObjectTypes?.())?.classes || [];
|
||||
const choices = [...new Set(classes)];
|
||||
return {
|
||||
hide: !objectDetector,
|
||||
choices,
|
||||
|
||||
@@ -104,7 +104,8 @@ export class SmartOccupancySensor extends ScryptedDeviceBase implements Settings
|
||||
|
||||
this.storageSettings.settings.detections.onGet = async () => {
|
||||
const objectDetection: ObjectDetection = this.storageSettings.values.objectDetection;
|
||||
const choices = (await objectDetection?.getDetectionModel())?.classes || [];
|
||||
const classes = (await objectDetection?.getDetectionModel())?.classes || []
|
||||
const choices = [...new Set(classes)];
|
||||
return {
|
||||
hide: !objectDetection,
|
||||
choices,
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import sdk from '@scrypted/sdk';
|
||||
|
||||
export function safeParseJson(value: string) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllDevices() {
|
||||
return Object.keys(sdk.systemManager.getSystemState()).map(id => sdk.systemManager.getDeviceById(id));
|
||||
}
|
||||
20
plugins/onnx/.vscode/settings.json
vendored
20
plugins/onnx/.vscode/settings.json
vendored
@@ -1,20 +1,4 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
"scrypted.serverRoot": "/server",
|
||||
|
||||
// pi local installation
|
||||
// "scrypted.debugHost": "192.168.2.119",
|
||||
// "scrypted.serverRoot": "/home/pi/.scrypted",
|
||||
|
||||
// local checkout
|
||||
// "scrypted.debugHost": "127.0.0.1",
|
||||
// "scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "koushik-winvm",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
}
|
||||
"scrypted.debugHost": "koushik-ubuntu24",
|
||||
}
|
||||
4
plugins/onnx/package-lock.json
generated
4
plugins/onnx/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.120",
|
||||
"version": "0.1.127",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.120",
|
||||
"version": "0.1.127",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
"runtime": "python",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"ScryptedSystemDevice",
|
||||
"DeviceCreator",
|
||||
"DeviceProvider",
|
||||
"Settings",
|
||||
"ClusterForkInterface",
|
||||
@@ -48,5 +50,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.120"
|
||||
"version": "0.1.127"
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ from PIL import Image
|
||||
from scrypted_sdk.other import SettingValue
|
||||
from scrypted_sdk.types import Setting
|
||||
|
||||
from .custom_detection import ONNXCustomDetection
|
||||
import common.yolo as yolo
|
||||
from predict import PredictPlugin
|
||||
|
||||
from .face_recognition import ONNXFaceRecognition
|
||||
from .clip_embedding import ONNXClipEmbedding
|
||||
|
||||
try:
|
||||
from .text_recognition import ONNXTextRecognition
|
||||
@@ -58,6 +60,8 @@ class ONNXPlugin(
|
||||
def __init__(self, nativeId: str | None = None, forked: bool = False):
|
||||
super().__init__(nativeId=nativeId, forked=forked)
|
||||
|
||||
self.custom_models = {}
|
||||
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
if model == "Default" or model not in availableModels:
|
||||
if model != "Default":
|
||||
@@ -162,13 +166,14 @@ class ONNXPlugin(
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
self.clipDevice = None
|
||||
|
||||
if not self.forked:
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
devices = [
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
@@ -178,10 +183,10 @@ class ONNXPlugin(
|
||||
],
|
||||
"name": "ONNX Face Recognition",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
if ONNXTextRecognition:
|
||||
devices.append(
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
@@ -193,9 +198,17 @@ class ONNXPlugin(
|
||||
},
|
||||
)
|
||||
|
||||
await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"devices": devices,
|
||||
"nativeId": "clipembedding",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
scrypted_sdk.ScryptedInterface.TextEmbedding.value,
|
||||
scrypted_sdk.ScryptedInterface.ImageEmbedding.value,
|
||||
],
|
||||
"name": "ONNX CLIP Embedding",
|
||||
}
|
||||
)
|
||||
except:
|
||||
@@ -208,7 +221,16 @@ class ONNXPlugin(
|
||||
elif nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or ONNXTextRecognition(self, nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
elif nativeId == "clipembedding":
|
||||
self.clipDevice = self.clipDevice or ONNXClipEmbedding(self, nativeId)
|
||||
return self.clipDevice
|
||||
custom_model = self.custom_models.get(nativeId, None)
|
||||
if custom_model:
|
||||
return custom_model
|
||||
custom_model = ONNXCustomDetection(self, nativeId)
|
||||
self.custom_models[nativeId] = custom_model
|
||||
await custom_model.reportDevice(nativeId, custom_model.providedName)
|
||||
return custom_model
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
|
||||
122
plugins/onnx/src/ort/clip_embedding.py
Normal file
122
plugins/onnx/src/ort/clip_embedding.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime
|
||||
from PIL import Image
|
||||
import threading
|
||||
|
||||
from predict.clip import ClipEmbedding
|
||||
from scrypted_sdk import ObjectsDetected
|
||||
import concurrent.futures
|
||||
import sys
|
||||
import platform
|
||||
|
||||
|
||||
class ONNXClipEmbedding(ClipEmbedding):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
|
||||
def getFiles(self):
|
||||
return [
|
||||
"text.onnx",
|
||||
"vision.onnx",
|
||||
]
|
||||
|
||||
def loadModel(self, files):
|
||||
# find the xml file in the files list
|
||||
text_onnx = [f for f in files if f.lower().endswith('text.onnx')]
|
||||
if not text_onnx:
|
||||
raise ValueError("No onnx model file found in the provided files list")
|
||||
text_onnx = text_onnx[0]
|
||||
|
||||
vision_onnx = [f for f in files if f.lower().endswith('vision.onnx')]
|
||||
if not vision_onnx:
|
||||
raise ValueError("No onnx model file found in the provided files list")
|
||||
vision_onnx = vision_onnx[0]
|
||||
|
||||
|
||||
compiled_models_array = []
|
||||
compiled_models = {}
|
||||
deviceIds = self.plugin.deviceIds
|
||||
|
||||
for deviceId in deviceIds:
|
||||
sess_options = onnxruntime.SessionOptions()
|
||||
|
||||
providers: list[str] = []
|
||||
if sys.platform == "darwin":
|
||||
providers.append("CoreMLExecutionProvider")
|
||||
|
||||
if "linux" in sys.platform and platform.machine() == "x86_64":
|
||||
deviceId = int(deviceId)
|
||||
providers.append(("CUDAExecutionProvider", {"device_id": deviceId}))
|
||||
|
||||
providers.append("CPUExecutionProvider")
|
||||
|
||||
text_model = onnxruntime.InferenceSession(
|
||||
text_onnx, sess_options=sess_options, providers=providers
|
||||
)
|
||||
vision_model = onnxruntime.InferenceSession(
|
||||
vision_onnx, sess_options=sess_options, providers=providers
|
||||
)
|
||||
compiled_models_array.append((text_model, vision_model))
|
||||
|
||||
def executor_initializer():
|
||||
thread_name = threading.current_thread().name
|
||||
interpreter = compiled_models_array.pop()
|
||||
compiled_models[thread_name] = interpreter
|
||||
print("Runtime initialized on thread {}".format(thread_name))
|
||||
|
||||
executor = concurrent.futures.ThreadPoolExecutor(
|
||||
initializer=executor_initializer,
|
||||
max_workers=len(compiled_models_array),
|
||||
thread_name_prefix="custom",
|
||||
)
|
||||
|
||||
return compiled_models, executor
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
compiled_models, executor = self.model
|
||||
def predict():
|
||||
inputs = self.processor(images=input, return_tensors="np", padding="max_length", truncation=True)
|
||||
compiled_model = compiled_models[threading.current_thread().name]
|
||||
_, vision_session = compiled_model
|
||||
vision_predictions = vision_session.run(None, {vision_session.get_inputs()[0].name: inputs.data['pixel_values']})
|
||||
image_embeds = vision_predictions[0]
|
||||
# this is a hack to utilize the existing image massaging infrastructure
|
||||
embedding = bytearray(image_embeds.astype(np.float32).tobytes())
|
||||
ret: ObjectsDetected = {
|
||||
"detections": [
|
||||
{
|
||||
"embedding": embedding,
|
||||
}
|
||||
],
|
||||
"inputDimensions": src_size
|
||||
}
|
||||
return ret
|
||||
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, predict
|
||||
)
|
||||
return objs
|
||||
|
||||
async def getTextEmbedding(self, input):
|
||||
compiled_models, executor = self.model
|
||||
def predict():
|
||||
inputs = self.processor(text=input, return_tensors="np", padding="max_length", truncation=True)
|
||||
compiled_model = compiled_models[threading.current_thread().name]
|
||||
text_session, _ = compiled_model
|
||||
text_inputs = {
|
||||
text_session.get_inputs()[0].name: inputs['input_ids'],
|
||||
text_session.get_inputs()[1].name: inputs['attention_mask']
|
||||
}
|
||||
text_predictions = text_session.run(None, text_inputs)
|
||||
text_embeds = text_predictions[0]
|
||||
return bytearray(text_embeds.astype(np.float32).tobytes())
|
||||
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, predict
|
||||
)
|
||||
return objs
|
||||
127
plugins/onnx/src/ort/custom_detection.py
Normal file
127
plugins/onnx/src/ort/custom_detection.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import onnxruntime
|
||||
import sys
|
||||
import threading
|
||||
import platform
|
||||
|
||||
from predict.custom_detect import CustomDetection
|
||||
from scrypted_sdk import ObjectsDetected
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
class ONNXCustomDetection(CustomDetection):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
self.prefer_relu = True
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-custom")
|
||||
|
||||
def loadModel(self, files: list[str]):
|
||||
# find the xml file in the files list
|
||||
onnx_files = [f for f in files if f.lower().endswith('.onnx')]
|
||||
if not onnx_files:
|
||||
raise ValueError("No Manifest.json file found in the provided files list")
|
||||
onnx_file = onnx_files[0]
|
||||
|
||||
compiled_models_array = []
|
||||
compiled_models = {}
|
||||
deviceIds = self.plugin.deviceIds
|
||||
|
||||
for deviceId in deviceIds:
|
||||
sess_options = onnxruntime.SessionOptions()
|
||||
|
||||
providers: list[str] = []
|
||||
if sys.platform == "darwin":
|
||||
providers.append("CoreMLExecutionProvider")
|
||||
|
||||
if "linux" in sys.platform and platform.machine() == "x86_64":
|
||||
deviceId = int(deviceId)
|
||||
providers.append(("CUDAExecutionProvider", {"device_id": deviceId}))
|
||||
|
||||
providers.append("CPUExecutionProvider")
|
||||
|
||||
compiled_model = onnxruntime.InferenceSession(
|
||||
onnx_file, sess_options=sess_options, providers=providers
|
||||
)
|
||||
compiled_models_array.append(compiled_model)
|
||||
|
||||
input = compiled_model.get_inputs()[0]
|
||||
input_name = input.name
|
||||
|
||||
def executor_initializer():
|
||||
thread_name = threading.current_thread().name
|
||||
interpreter = compiled_models_array.pop()
|
||||
compiled_models[thread_name] = interpreter
|
||||
print("Runtime initialized on thread {}".format(thread_name))
|
||||
|
||||
executor = concurrent.futures.ThreadPoolExecutor(
|
||||
initializer=executor_initializer,
|
||||
max_workers=len(compiled_models_array),
|
||||
thread_name_prefix="custom",
|
||||
)
|
||||
|
||||
prepareExecutor = concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=len(compiled_models_array),
|
||||
thread_name_prefix="custom-prepare",
|
||||
)
|
||||
|
||||
return compiled_models, input_name, prepareExecutor, executor
|
||||
|
||||
|
||||
async def predictModel(self, input: Image.Image) -> ObjectsDetected:
|
||||
compiled_models, input_name, prepareExecutor, executor = self.model
|
||||
def predict():
|
||||
if self.model_config.get("mean", None) and self.model_config.get("std", None):
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
|
||||
mean = np.array(self.model_config["mean"])
|
||||
std = np.array(self.model_config["std"])
|
||||
mean = mean.reshape(1, -1, 1, 1)
|
||||
std = std.reshape(1, -1, 1, 1)
|
||||
im = (im - mean) / std
|
||||
|
||||
im = np.ascontiguousarray(im.astype(np.float32)) # contiguous
|
||||
|
||||
out_dict = model.predict({inputName: im})
|
||||
else:
|
||||
out_dict = self.model.predict({self.inputName: input})
|
||||
|
||||
results = list(out_dict.values())[0][0]
|
||||
return results
|
||||
|
||||
def prepare():
|
||||
im = np.array(input)
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
|
||||
if self.model_config.get("mean", None) and self.model_config.get("std", None):
|
||||
mean = np.array(self.model_config["mean"])
|
||||
std = np.array(self.model_config["std"])
|
||||
mean = mean.reshape(1, -1, 1, 1)
|
||||
std = std.reshape(1, -1, 1, 1)
|
||||
im = (im - mean) / std
|
||||
im = im.astype(np.float32)
|
||||
|
||||
im = np.ascontiguousarray(im)
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
compiled_model = compiled_models[threading.current_thread().name]
|
||||
output_tensors = compiled_model.run(None, {input_name: input_tensor})
|
||||
return output_tensors
|
||||
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
prepareExecutor, lambda: prepare()
|
||||
)
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, lambda: predict(input_tensor)
|
||||
)
|
||||
|
||||
return objs[0][0]
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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.19.2; 'darwin' not in sys_platform and platform_machine != 'aarch64'
|
||||
onnxruntime-gpu==1.22.0; 'darwin' not in sys_platform and platform_machine != 'aarch64'
|
||||
# cpu and coreml execution provider
|
||||
onnxruntime; 'darwin' in sys_platform or platform_machine == 'aarch64'
|
||||
# nightly?
|
||||
@@ -9,3 +9,5 @@ onnxruntime; 'darwin' in sys_platform or platform_machine == 'aarch64'
|
||||
|
||||
Pillow==10.3.0
|
||||
opencv-python-headless==4.10.0.84
|
||||
|
||||
transformers==4.52.4
|
||||
|
||||
84
plugins/openvino/package-lock.json
generated
84
plugins/openvino/package-lock.json
generated
@@ -1,36 +1,43 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.177",
|
||||
"version": "0.1.185",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.177",
|
||||
"version": "0.1.185",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.29",
|
||||
"version": "0.5.20",
|
||||
"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.27.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.5",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.10.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"openai": "^5.3.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",
|
||||
"rollup": "^4.43.0",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.3",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
@@ -42,11 +49,9 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
"@types/node": "^24.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.28.5"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
@@ -61,25 +66,30 @@
|
||||
"@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.27.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.5",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-virtual": "^3.0.2",
|
||||
"@types/node": "^24.0.1",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.10.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"openai": "^5.3.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.43.0",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typedoc": "^0.28.5",
|
||||
"typescript": "^5.8.3",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
"runtime": "python",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"ScryptedSystemDevice",
|
||||
"DeviceCreator",
|
||||
"DeviceProvider",
|
||||
"Settings",
|
||||
"ClusterForkInterface",
|
||||
@@ -48,5 +50,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.177"
|
||||
"version": "0.1.185"
|
||||
}
|
||||
|
||||
25
plugins/openvino/src/common/path_tools.py
Normal file
25
plugins/openvino/src/common/path_tools.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
def replace_last_path_component(url, new_path):
|
||||
# Parse the original URL
|
||||
parsed_url = urlparse(url)
|
||||
|
||||
# Split the path into components
|
||||
path_components = parsed_url.path.split('/')
|
||||
|
||||
# Remove the last component
|
||||
if len(path_components) > 1:
|
||||
path_components.pop()
|
||||
else:
|
||||
raise ValueError("URL path has no components to replace")
|
||||
|
||||
# Join the path components back together
|
||||
new_path = '/'.join(path_components) + '/' + new_path
|
||||
|
||||
# Create a new parsed URL with the updated path
|
||||
new_parsed_url = parsed_url._replace(path=new_path)
|
||||
|
||||
# Reconstruct the URL
|
||||
new_url = urlunparse(new_parsed_url)
|
||||
|
||||
return new_url
|
||||
@@ -18,6 +18,7 @@ import common.yolo as yolo
|
||||
from predict import Prediction, PredictPlugin
|
||||
from predict.rectangle import Rectangle
|
||||
|
||||
from .custom_detection import OpenVINOCustomDetection
|
||||
from .face_recognition import OpenVINOFaceRecognition
|
||||
|
||||
try:
|
||||
@@ -25,6 +26,8 @@ try:
|
||||
except:
|
||||
OpenVINOTextRecognition = None
|
||||
|
||||
from .clip_embedding import OpenVINOClipEmbedding
|
||||
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(
|
||||
thread_name_prefix="OpenVINO-Predict"
|
||||
)
|
||||
@@ -104,6 +107,8 @@ class OpenVINOPlugin(
|
||||
def __init__(self, nativeId: str | None = None, forked: bool = False):
|
||||
super().__init__(nativeId=nativeId, forked=forked)
|
||||
|
||||
self.custom_models = {}
|
||||
|
||||
self.core = ov.Core()
|
||||
dump_device_properties(self.core)
|
||||
available_devices = self.core.available_devices
|
||||
@@ -297,6 +302,7 @@ class OpenVINOPlugin(
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
self.clipDevice = None
|
||||
|
||||
if not self.forked:
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
@@ -395,7 +401,7 @@ class OpenVINOPlugin(
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
devices = [
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
@@ -405,10 +411,10 @@ class OpenVINOPlugin(
|
||||
],
|
||||
"name": "OpenVINO Face Recognition",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
if OpenVINOTextRecognition:
|
||||
devices.append(
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
@@ -417,12 +423,20 @@ class OpenVINOPlugin(
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "OpenVINO Text Recognition",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"devices": devices,
|
||||
"nativeId": "clipembedding",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
scrypted_sdk.ScryptedInterface.TextEmbedding.value,
|
||||
scrypted_sdk.ScryptedInterface.ImageEmbedding.value,
|
||||
],
|
||||
"name": "OpenVINO CLIP Embedding",
|
||||
}
|
||||
)
|
||||
except:
|
||||
@@ -435,4 +449,13 @@ class OpenVINOPlugin(
|
||||
elif nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or OpenVINOTextRecognition(self, nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
elif nativeId == "clipembedding":
|
||||
self.clipDevice = self.clipDevice or OpenVINOClipEmbedding(self, nativeId)
|
||||
return self.clipDevice
|
||||
custom_model = self.custom_models.get(nativeId, None)
|
||||
if custom_model:
|
||||
return custom_model
|
||||
custom_model = OpenVINOCustomDetection(self, nativeId)
|
||||
self.custom_models[nativeId] = custom_model
|
||||
await custom_model.reportDevice(nativeId, custom_model.providedName)
|
||||
return custom_model
|
||||
|
||||
@@ -2,6 +2,6 @@ import concurrent.futures
|
||||
|
||||
|
||||
def create_executors(name: str):
|
||||
prepare = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Prepare")
|
||||
predict = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Predict")
|
||||
prepare = concurrent.futures.ThreadPoolExecutor(1, f"{name}Prepare")
|
||||
predict = concurrent.futures.ThreadPoolExecutor(1, f"{name}Predict")
|
||||
return prepare, predict
|
||||
|
||||
87
plugins/openvino/src/ov/clip_embedding.py
Normal file
87
plugins/openvino/src/ov/clip_embedding.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import openvino.runtime as ov
|
||||
from PIL import Image
|
||||
|
||||
from ov import async_infer
|
||||
from predict.clip import ClipEmbedding
|
||||
from scrypted_sdk import ObjectsDetected
|
||||
|
||||
clipPrepare, clipPredict = async_infer.create_executors("ClipPredict")
|
||||
|
||||
|
||||
# _int8 is available but seems slower in addition to the accuracy loss.
|
||||
model_suffix = ""
|
||||
text_xml_name = f"text{model_suffix}.xml"
|
||||
vision_xml_name = f"vision{model_suffix}.xml"
|
||||
|
||||
class OpenVINOClipEmbedding(ClipEmbedding):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
|
||||
def getFiles(self):
|
||||
return [
|
||||
f"openvino/{text_xml_name}",
|
||||
f"openvino/text{model_suffix}.bin",
|
||||
f"openvino/{vision_xml_name}",
|
||||
f"openvino/vision{model_suffix}.bin"
|
||||
]
|
||||
|
||||
def loadModel(self, files):
|
||||
# find the xml file in the files list
|
||||
text_xml = [f for f in files if f.lower().endswith(text_xml_name)]
|
||||
if not text_xml:
|
||||
raise ValueError("No XML model file found in the provided files list")
|
||||
text_xml = text_xml[0]
|
||||
|
||||
vision_xml = [f for f in files if f.lower().endswith(vision_xml_name)]
|
||||
if not vision_xml:
|
||||
raise ValueError("No XML model file found in the provided files list")
|
||||
vision_xml = vision_xml[0]
|
||||
|
||||
textModel = self.plugin.core.compile_model(text_xml, self.plugin.mode)
|
||||
model = self.plugin.core.read_model(vision_xml)
|
||||
# for some reason this is exporting as dynamic axes and causing npu to crash
|
||||
model.reshape([1, 3, 224, 224])
|
||||
visionModel = self.plugin.core.compile_model(model, self.plugin.mode)
|
||||
return textModel, visionModel
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
def predict():
|
||||
inputs = self.processor(images=input, return_tensors="np", padding="max_length", truncation=True)
|
||||
_, vision_model = self.model
|
||||
vision_predictions = vision_model(inputs.data['pixel_values'])
|
||||
image_embeds = vision_predictions[0]
|
||||
# this is a hack to utilize the existing image massaging infrastructure
|
||||
embedding = bytearray(image_embeds.astype(np.float32).tobytes())
|
||||
ret: ObjectsDetected = {
|
||||
"detections": [
|
||||
{
|
||||
"embedding": embedding,
|
||||
}
|
||||
],
|
||||
"inputDimensions": src_size
|
||||
}
|
||||
return ret
|
||||
|
||||
ret = await asyncio.get_event_loop().run_in_executor(
|
||||
clipPredict, lambda: predict()
|
||||
)
|
||||
return ret
|
||||
|
||||
async def getTextEmbedding(self, input):
|
||||
def predict():
|
||||
inputs = self.processor(text=input, return_tensors="np", padding="max_length", truncation=True)
|
||||
text_model, _ = self.model
|
||||
text_predictions = text_model((inputs.data['input_ids'], inputs.data['attention_mask']))
|
||||
text_embeds = text_predictions[0]
|
||||
return bytearray(text_embeds.astype(np.float32).tobytes())
|
||||
|
||||
ret = await asyncio.get_event_loop().run_in_executor(
|
||||
clipPredict, lambda: predict()
|
||||
)
|
||||
return ret
|
||||
56
plugins/openvino/src/ov/custom_detection.py
Normal file
56
plugins/openvino/src/ov/custom_detection.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import numpy as np
|
||||
import openvino.runtime as ov
|
||||
from PIL import Image
|
||||
|
||||
from ov import async_infer
|
||||
from predict.custom_detect import CustomDetection
|
||||
from scrypted_sdk import ObjectsDetected
|
||||
|
||||
customDetectPrepare, customDetectPredict = async_infer.create_executors("CustomDetect")
|
||||
|
||||
|
||||
class OpenVINOCustomDetection(CustomDetection):
|
||||
def __init__(self, plugin, nativeId: str):
|
||||
super().__init__(plugin=plugin, nativeId=nativeId)
|
||||
self.prefer_relu = True
|
||||
|
||||
def loadModel(self, files: list[str]):
|
||||
# find the xml file in the files list
|
||||
xml_files = [f for f in files if f.lower().endswith('.xml')]
|
||||
if not xml_files:
|
||||
raise ValueError("No XML model file found in the provided files list")
|
||||
xmlFile = xml_files[0]
|
||||
|
||||
return self.plugin.core.compile_model(xmlFile, self.plugin.mode)
|
||||
|
||||
async def predictModel(self, input: Image.Image) -> ObjectsDetected:
|
||||
def predict():
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
|
||||
if self.model_config.get("mean", None) and self.model_config.get("std", None):
|
||||
mean = np.array(self.model_config["mean"]).astype(np.float32)
|
||||
std = np.array(self.model_config["std"]).astype(np.float32)
|
||||
mean = mean.reshape(1, -1, 1, 1)
|
||||
std = std.reshape(1, -1, 1, 1)
|
||||
im = (im - mean) / std
|
||||
im = im.astype(np.float32)
|
||||
|
||||
im = np.ascontiguousarray(im)
|
||||
|
||||
infer_request = self.model.create_infer_request()
|
||||
tensor = ov.Tensor(array=im)
|
||||
infer_request.set_input_tensor(tensor)
|
||||
output_tensors = infer_request.infer()
|
||||
ret = output_tensors[0][0]
|
||||
return ret
|
||||
|
||||
ret = await asyncio.get_event_loop().run_in_executor(
|
||||
customDetectPredict, lambda: predict()
|
||||
)
|
||||
return ret
|
||||
@@ -32,7 +32,9 @@ class OpenVINOTextRecognition(TextRecognition):
|
||||
model.reshape([1, 1, 64, 384])
|
||||
return self.plugin.core.compile_model(model, self.plugin.mode)
|
||||
else:
|
||||
return self.plugin.core.compile_model(xmlFile, self.plugin.mode)
|
||||
model = self.plugin.core.read_model(xmlFile)
|
||||
model.reshape([1, 3, 640, 640])
|
||||
return self.plugin.core.compile_model(model, self.plugin.mode)
|
||||
|
||||
async def predictDetectModel(self, input: np.ndarray):
|
||||
def predict():
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import asyncio
|
||||
import math
|
||||
import os
|
||||
@@ -45,7 +48,7 @@ class Prediction:
|
||||
self.embedding = embedding
|
||||
|
||||
|
||||
class PredictPlugin(DetectPlugin, scrypted_sdk.ClusterForkInterface):
|
||||
class PredictPlugin(DetectPlugin, scrypted_sdk.ClusterForkInterface, scrypted_sdk.ScryptedSystemDevice, scrypted_sdk.DeviceCreator, scrypted_sdk.DeviceProvider):
|
||||
labels: dict
|
||||
|
||||
def __init__(
|
||||
@@ -56,6 +59,10 @@ class PredictPlugin(DetectPlugin, scrypted_sdk.ClusterForkInterface):
|
||||
):
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
self.systemDevice = {
|
||||
"deviceCreator": "Model",
|
||||
}
|
||||
|
||||
self.plugin = plugin
|
||||
# self.clusterIndex = 0
|
||||
|
||||
@@ -356,6 +363,10 @@ class PredictPlugin(DetectPlugin, scrypted_sdk.ClusterForkInterface):
|
||||
ret = await result.getTextRecognition()
|
||||
elif self.nativeId == "facerecognition":
|
||||
ret = await result.getFaceRecognition()
|
||||
elif self.nativeId == "clipembedding":
|
||||
ret = await result.getClipEmbedding()
|
||||
else:
|
||||
ret = await result.getCustomDetection(self.nativeId)
|
||||
return ret
|
||||
|
||||
async def startCluster(self):
|
||||
@@ -394,6 +405,79 @@ class PredictPlugin(DetectPlugin, scrypted_sdk.ClusterForkInterface):
|
||||
asyncio.ensure_future(startClusterWorker(), loop=self.loop)
|
||||
|
||||
|
||||
async def getCreateDeviceSettings(self):
|
||||
ret: list[Setting] = []
|
||||
|
||||
ret.append({
|
||||
"key": "name",
|
||||
"title": "Model Name",
|
||||
"description": "The name or description of this model. E.g., Bird Classifier."
|
||||
})
|
||||
|
||||
ret.append({
|
||||
"key": "url",
|
||||
"title": "Model URL",
|
||||
"description": "The URL of the model. This should be a Github repo or url path to the model's config.json."
|
||||
})
|
||||
|
||||
ret.append({
|
||||
"key": "info",
|
||||
"type": "html",
|
||||
"title": "Sample Model",
|
||||
"value": "<a href='https://github.com/scryptedapp/bird-classifier'>A reference bird classification model.</a>"
|
||||
})
|
||||
return ret
|
||||
|
||||
async def createDevice(self, settings):
|
||||
name = settings.get('name', None)
|
||||
if not name:
|
||||
raise Exception("Model name not provided")
|
||||
model_url: str = settings.get('url', None)
|
||||
if not model_url:
|
||||
raise Exception("Model URL not provided")
|
||||
if not model_url.endswith('config.json'):
|
||||
plugin_suffix = self.pluginId.split('/')[1]
|
||||
match = re.match(r'https://github\.com/([^/]+)/([^/]+)', model_url)
|
||||
if not match:
|
||||
raise ValueError("Invalid GitHub repository URL.")
|
||||
|
||||
org, repo = match.groups()
|
||||
model_url = f"https://raw.githubusercontent.com/{org}/{repo}/refs/heads/main/models/{plugin_suffix}/config.json"
|
||||
|
||||
response = urllib.request.urlopen(model_url)
|
||||
if response.getcode() < 200 or response.getcode() >= 300:
|
||||
raise Exception(f"non-2xx response code")
|
||||
data = response.read()
|
||||
|
||||
config = json.loads(data)
|
||||
|
||||
nativeId = ''.join(random.choices('0123456789abcdef', k=8))
|
||||
|
||||
id = await self.reportDevice(nativeId, name)
|
||||
|
||||
from .custom_detect import CustomDetection
|
||||
device: CustomDetection = await self.getDevice(nativeId)
|
||||
device.storage.setItem("config_url", model_url)
|
||||
device.storage.setItem("config", json.dumps(config))
|
||||
device.init_model()
|
||||
|
||||
return id
|
||||
|
||||
async def reportDevice(self, nativeId: str, name: str):
|
||||
return await scrypted_sdk.deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
"nativeId": nativeId,
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
scrypted_sdk.ScryptedInterface.Settings.value,
|
||||
"CustomObjectDetection",
|
||||
],
|
||||
"name": name,
|
||||
},
|
||||
)
|
||||
|
||||
class Fork:
|
||||
def __init__(self, PluginType: Any):
|
||||
if PluginType:
|
||||
@@ -409,3 +493,9 @@ class Fork:
|
||||
|
||||
async def getFaceRecognition(self):
|
||||
return await self.plugin.getDevice("facerecognition")
|
||||
|
||||
async def getClipEmbedding(self):
|
||||
return await self.plugin.getDevice("clipembedding")
|
||||
|
||||
async def getCustomDetection(self, nativeId: str):
|
||||
return await self.plugin.getDevice(nativeId)
|
||||
|
||||
63
plugins/openvino/src/predict/clip.py
Normal file
63
plugins/openvino/src/predict/clip.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import os
|
||||
from typing import Tuple
|
||||
|
||||
import scrypted_sdk
|
||||
from transformers import CLIPProcessor
|
||||
|
||||
from predict import PredictPlugin
|
||||
|
||||
|
||||
class ClipEmbedding(PredictPlugin, scrypted_sdk.TextEmbedding, scrypted_sdk.ImageEmbedding):
|
||||
def __init__(self, plugin: PredictPlugin, nativeId: str):
|
||||
super().__init__(nativeId=nativeId, plugin=plugin)
|
||||
|
||||
self.inputwidth = 224
|
||||
self.inputheight = 224
|
||||
|
||||
self.labels = {}
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.minThreshold = 0.5
|
||||
|
||||
self.model = self.initModel()
|
||||
self.processor = CLIPProcessor.from_pretrained(
|
||||
"openai/clip-vit-base-patch32",
|
||||
cache_dir=os.path.join(os.environ["SCRYPTED_PLUGIN_VOLUME"], "files", "hf"),
|
||||
)
|
||||
|
||||
def getFiles(self):
|
||||
pass
|
||||
|
||||
def initModel(self):
|
||||
local_files: list[str] = []
|
||||
for file in self.getFiles():
|
||||
remote_file = "https://huggingface.co/koushd/clip/resolve/main/" + file
|
||||
localFile = self.downloadFile(remote_file, f"{self.id}/{file}")
|
||||
local_files.append(localFile)
|
||||
return self.loadModel(local_files)
|
||||
|
||||
def loadModel(self, files: list[str]):
|
||||
pass
|
||||
|
||||
async def getImageEmbedding(self, input):
|
||||
detections = await super().detectObjects(input, None)
|
||||
return detections["detections"][0]["embedding"]
|
||||
|
||||
async def detectObjects(self, mediaObject, session = None):
|
||||
ret = await super().detectObjects(mediaObject, session)
|
||||
embedding = ret["detections"][0]['embedding']
|
||||
ret["detections"][0]['embedding'] = base64.b64encode(embedding).decode("utf-8")
|
||||
return ret
|
||||
|
||||
# width, height, channels
|
||||
def get_input_details(self) -> Tuple[int, int, int]:
|
||||
return (self.inputwidth, self.inputheight, 3)
|
||||
|
||||
def get_input_size(self) -> Tuple[float, float]:
|
||||
return (self.inputwidth, self.inputheight)
|
||||
|
||||
def get_input_format(self) -> str:
|
||||
return "rgb"
|
||||
135
plugins/openvino/src/predict/custom_detect.py
Normal file
135
plugins/openvino/src/predict/custom_detect.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any, List, Tuple
|
||||
import json
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from scrypted_sdk import (ObjectDetectionResult, ObjectDetectionSession,
|
||||
ObjectsDetected)
|
||||
import scrypted_sdk
|
||||
|
||||
from common import yolo
|
||||
from predict import PredictPlugin
|
||||
from common import softmax
|
||||
|
||||
from common.path_tools import replace_last_path_component
|
||||
|
||||
def safe_parse_json(value: str):
|
||||
try:
|
||||
return json.loads(value)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
class CustomDetection(PredictPlugin, scrypted_sdk.Settings):
|
||||
def __init__(self, plugin: PredictPlugin, nativeId: str):
|
||||
super().__init__(nativeId=nativeId, plugin=plugin)
|
||||
|
||||
if not hasattr(self, "prefer_relu"):
|
||||
self.prefer_relu = False
|
||||
|
||||
self.inputheight = 320
|
||||
self.inputwidth = 320
|
||||
|
||||
self.labels = {}
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.minThreshold = 0.5
|
||||
|
||||
self.init_model()
|
||||
|
||||
# self.detectModel = self.downloadModel("scrypted_yolov9t_relu_face_320" if self.prefer_relu else "scrypted_yolov9t_face_320")
|
||||
# self.faceModel = self.downloadModel("inception_resnet_v1")
|
||||
|
||||
def init_model(self):
|
||||
config_url = self.storage.getItem('config_url')
|
||||
if not config_url:
|
||||
return
|
||||
config_str = self.storage.getItem('config')
|
||||
if not config_str:
|
||||
return
|
||||
config = json.loads(config_str)
|
||||
self.model_config = config
|
||||
for key in self.model_config['labels']:
|
||||
self.labels[int(key)] = self.model_config['labels'][key]
|
||||
self.inputwidth = config["input_shape"][2]
|
||||
self.inputheight = config["input_shape"][3]
|
||||
files: list[str] = config["files"]
|
||||
local_files: list[str] = []
|
||||
for file in files:
|
||||
remote_file = replace_last_path_component(config_url, file)
|
||||
localFile = self.downloadFile(remote_file, f"{self.id}/{file}")
|
||||
local_files.append(localFile)
|
||||
|
||||
self.model = self.loadModel(local_files)
|
||||
|
||||
def loadModel(self, files: list[str]):
|
||||
pass
|
||||
|
||||
# width, height, channels
|
||||
def get_input_details(self) -> Tuple[int, int, int]:
|
||||
return (self.inputwidth, self.inputheight, 3)
|
||||
|
||||
def get_input_size(self) -> Tuple[float, float]:
|
||||
return (self.inputwidth, self.inputheight)
|
||||
|
||||
def get_input_format(self) -> str:
|
||||
return "rgb"
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
results = await self.predictModel(input)
|
||||
if self.model_config["model"] == "yolov9":
|
||||
objs = yolo.parse_yolov9(results)
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
elif self.model_config["model"] == "resnet":
|
||||
exclude_classes = safe_parse_json(self.storage.getItem('excludeClasses')) or []
|
||||
while len(exclude_classes):
|
||||
excluded_class = exclude_classes.pop()
|
||||
for idx, class_name in self.labels.items():
|
||||
if class_name == excluded_class:
|
||||
results[idx] = 0
|
||||
sm = softmax.softmax(results)
|
||||
# get anything over the threshold, sort by score, top 3
|
||||
min_indexes = np.where(sm > self.minThreshold)[0]
|
||||
min_indexes = min_indexes[np.argsort(sm[min_indexes])[::-1]]
|
||||
min_indexes = min_indexes[:3]
|
||||
detection_result: ObjectsDetected = {}
|
||||
detections: List[ObjectDetectionResult] = []
|
||||
detection_result["detections"] = detections
|
||||
detection_result["inputDimensions"] = src_size
|
||||
for idx in min_indexes:
|
||||
label = self.labels[int(idx)]
|
||||
score = float(sm[int(idx)])
|
||||
detections.append(
|
||||
{
|
||||
"className": label,
|
||||
"score": score,
|
||||
}
|
||||
)
|
||||
return detection_result
|
||||
else:
|
||||
raise ValueError("Unknown model type")
|
||||
|
||||
async def predictModel(self, input: Image.Image) -> ObjectsDetected:
|
||||
pass
|
||||
|
||||
async def getSettings(self):
|
||||
return [
|
||||
{
|
||||
'key': 'excludeClasses',
|
||||
'title': 'Exclude Classes',
|
||||
'description': 'Classes to exclude from detection.',
|
||||
'multiple': True,
|
||||
'choices': list(self.labels.values()),
|
||||
'value': safe_parse_json(self.storage.getItem('excludeClasses')),
|
||||
}
|
||||
]
|
||||
|
||||
async def putSetting(self, key: str, value: str):
|
||||
if value:
|
||||
self.storage.setItem(key, json.dumps(value))
|
||||
else:
|
||||
self.storage.removeItem(key)
|
||||
await scrypted_sdk.deviceManager.onDeviceEvent(self.nativeId, scrypted_sdk.ScryptedInterface.Settings.value, None)
|
||||
|
||||
@@ -7,3 +7,5 @@
|
||||
openvino==2024.5.0
|
||||
Pillow==10.3.0
|
||||
opencv-python-headless==4.10.0.84
|
||||
|
||||
transformers==4.52.4
|
||||
|
||||
817
plugins/prebuffer-mixin/package-lock.json
generated
817
plugins/prebuffer-mixin/package-lock.json
generated
@@ -1,24 +1,22 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.58",
|
||||
"version": "0.10.59",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.58",
|
||||
"version": "0.10.59",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/libav": "^1.0.199",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"h264-sps-parser": "^0.2.1",
|
||||
"semver": "^7.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/semver": "^7.3.12",
|
||||
"prebuild-install": "npm:@scrypted/prebuild-install@^7.1.9"
|
||||
"@types/semver": "^7.3.12"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -80,34 +78,10 @@
|
||||
"../sdk": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@isaacs/fs-minipass": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^7.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/libav": {
|
||||
"version": "1.0.199",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/libav/-/libav-1.0.199.tgz",
|
||||
"integrity": "sha512-sIrTRwa5CSZgnbCzPEB/laBNJ6RoZ3BKkVrgMCX/GE7oYCBcxIYtgFq942DVckXA6+A6NfXBR1lLt8a52SPpyQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"node-addon-api": "^8.3.1",
|
||||
"tar": "^7.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
@@ -128,369 +102,11 @@
|
||||
"integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"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",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/h264-sps-parser": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/h264-sps-parser/-/h264-sps-parser-0.2.1.tgz",
|
||||
"integrity": "sha512-1OCliBfrgCe2fu6eRRqN0xvhp9tFH/UVa1zdVmX/WcQAZrEB6xm/2RJ13F4h2OtQUMJqWv31fZakmTgrvbyIDA=="
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "dist/cjs/src/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.74.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
|
||||
"integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz",
|
||||
"integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"name": "@scrypted/prebuild-install",
|
||||
"version": "7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/prebuild-install/-/prebuild-install-7.1.9.tgz",
|
||||
"integrity": "sha512-lKKX7TMlwToQIH2UX8p0JwFbp0Y2dwedEqgkpcTmRvaA9DOfhau7m40i4+Qo7F1ISMHo/NK1XntURrFlSm4ibg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"dev": true,
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
@@ -503,133 +119,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"minizlib": "^3.0.1",
|
||||
"mkdirp": "^3.0.1",
|
||||
"yallist": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz",
|
||||
"integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs/node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
||||
"requires": {
|
||||
"minipass": "^7.0.4"
|
||||
}
|
||||
},
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
@@ -641,17 +139,6 @@
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/libav": {
|
||||
"version": "1.0.199",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/libav/-/libav-1.0.199.tgz",
|
||||
"integrity": "sha512-sIrTRwa5CSZgnbCzPEB/laBNJ6RoZ3BKkVrgMCX/GE7oYCBcxIYtgFq942DVckXA6+A6NfXBR1lLt8a52SPpyQ==",
|
||||
"requires": {
|
||||
"detect-libc": "^2.0.3",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"node-addon-api": "^8.3.1",
|
||||
"tar": "^7.4.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
@@ -695,319 +182,21 @@
|
||||
"integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==",
|
||||
"dev": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"dev": true
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
|
||||
},
|
||||
"fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true
|
||||
},
|
||||
"github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"dev": true
|
||||
},
|
||||
"h264-sps-parser": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/h264-sps-parser/-/h264-sps-parser-0.2.1.tgz",
|
||||
"integrity": "sha512-1OCliBfrgCe2fu6eRRqN0xvhp9tFH/UVa1zdVmX/WcQAZrEB6xm/2RJ13F4h2OtQUMJqWv31fZakmTgrvbyIDA=="
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
||||
"requires": {
|
||||
"minipass": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="
|
||||
},
|
||||
"mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
|
||||
"dev": true
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "3.74.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
|
||||
"integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^7.3.5"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz",
|
||||
"integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "npm:@scrypted/prebuild-install@7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/prebuild-install/-/prebuild-install-7.1.9.tgz",
|
||||
"integrity": "sha512-lKKX7TMlwToQIH2UX8p0JwFbp0Y2dwedEqgkpcTmRvaA9DOfhau7m40i4+Qo7F1ISMHo/NK1XntURrFlSm4ibg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"dev": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
||||
"requires": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"minizlib": "^3.0.1",
|
||||
"mkdirp": "^3.0.1",
|
||||
"yallist": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"tar-fs": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz",
|
||||
"integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.58",
|
||||
"version": "0.10.59",
|
||||
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
@@ -38,14 +38,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/libav": "^1.0.199",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"h264-sps-parser": "^0.2.1",
|
||||
"semver": "^7.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/semver": "^7.3.12",
|
||||
"prebuild-install": "npm:@scrypted/prebuild-install@^7.1.9"
|
||||
"@types/semver": "^7.3.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
import { Deferred } from "@scrypted/common/src/deferred";
|
||||
import { parseSdp } from "@scrypted/common/src/sdp-utils";
|
||||
import { sleep } from "@scrypted/common/src/sleep";
|
||||
import { StreamChunk } from "@scrypted/common/src/stream-parser";
|
||||
import { AVFormatContext, createAVFormatContext } from '@scrypted/libav';
|
||||
import { ResponseMediaStreamOptions } from "@scrypted/sdk";
|
||||
import { once } from 'events';
|
||||
import net from 'net';
|
||||
import { EventEmitter } from "stream";
|
||||
import tls from 'tls';
|
||||
import { RTSP_FRAME_MAGIC } from "../../../common/src/rtsp-server";
|
||||
import { ParserSession, setupActivityTimer } from "./ffmpeg-session";
|
||||
import { installLibavAddon } from "./libav-setup";
|
||||
import { negotiateMediaStream } from "./rfc4571";
|
||||
|
||||
let installPromise: Promise<void>;
|
||||
|
||||
export async function startLibavSession(console: Console, url: string, mediaStreamOptions: ResponseMediaStreamOptions, options: {
|
||||
useUdp: boolean,
|
||||
audioSoftMuted: boolean,
|
||||
activityTimeout: number,
|
||||
}): Promise<ParserSession<"rtsp">> {
|
||||
installPromise ||= installLibavAddon();
|
||||
await installPromise;
|
||||
|
||||
const formatContext = createAVFormatContext();
|
||||
try {
|
||||
return await startLibavSessionWrapped(formatContext, console, url, mediaStreamOptions, options);
|
||||
}
|
||||
catch (e) {
|
||||
await formatContext.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function startLibavSessionWrapped(formatContext: AVFormatContext, console: Console, url: string, mediaStreamOptions: ResponseMediaStreamOptions, options: {
|
||||
useUdp: boolean,
|
||||
audioSoftMuted: boolean,
|
||||
activityTimeout: number,
|
||||
}): Promise<ParserSession<"rtsp">> {
|
||||
const events = new EventEmitter();
|
||||
|
||||
let tlsProxy: net.Server;
|
||||
try {
|
||||
if (url.startsWith('rtsps:') || url.startsWith('https:')) {
|
||||
let { hostname, port } = new URL(url);
|
||||
if (!port) {
|
||||
if (url.startsWith('rtsps:'))
|
||||
port = '322';
|
||||
else
|
||||
port = '443';
|
||||
}
|
||||
|
||||
const portNumber = parseInt(port);
|
||||
if (!portNumber)
|
||||
throw new Error('invalid port number');
|
||||
|
||||
tlsProxy = net.createServer(async socket => {
|
||||
try {
|
||||
const tlsSocket = tls.connect({
|
||||
host: hostname,
|
||||
port: portNumber,
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
await once(tlsSocket, 'secureConnect');
|
||||
socket.pipe(tlsSocket).pipe(socket);
|
||||
}
|
||||
catch (e) {
|
||||
console.error('tls proxy error', e);
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
tlsProxy.listen(0, '127.0.0.1');
|
||||
await once(tlsProxy, 'listening');
|
||||
const localPort = (tlsProxy.address() as net.AddressInfo).port;
|
||||
// rewrite the url to use the local port
|
||||
const u = new URL(url);
|
||||
u.protocol = u.protocol.replace('s:', ':');
|
||||
u.hostname = '127.0.0.1';
|
||||
u.port = localPort.toString();
|
||||
url = u.toString();
|
||||
}
|
||||
|
||||
await formatContext.open(url, {
|
||||
rtsp_transport: options.useUdp ? 'udp' : 'tcp',
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
tlsProxy?.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
let sdp = formatContext.createSDP();
|
||||
const parsedSdp = parseSdp(sdp);
|
||||
// sdp may contain multiple audio/video sections. take only the first video section.
|
||||
sdp = [...parsedSdp.header.lines, ...parsedSdp.msections.map(msection => msection.lines).flat()].join('\r\n');
|
||||
|
||||
const killDeferred = new Deferred<void>();
|
||||
const startDeferred = new Deferred<void>();
|
||||
killDeferred.promise.catch(e => {
|
||||
events.emit('killed');
|
||||
events.emit('error', e);
|
||||
tlsProxy?.close();
|
||||
});
|
||||
|
||||
const kill = (e?: Error) => {
|
||||
killDeferred.reject(e || new Error('killed'));
|
||||
startDeferred.reject(e || new Error('killed'));
|
||||
}
|
||||
|
||||
const { resetActivityTimer } = setupActivityTimer('rtsp', kill, events, options?.activityTimeout);
|
||||
|
||||
(async () => {
|
||||
const pipelines: {
|
||||
streamIndex: number,
|
||||
writeFormatContext: AVFormatContext,
|
||||
}[] = [];
|
||||
|
||||
try {
|
||||
await startDeferred.promise;
|
||||
formatContext.streams.forEach(stream => {
|
||||
if (options.audioSoftMuted && stream.type === 'audio')
|
||||
return;
|
||||
if (stream.type !== 'video' && stream.type !== 'audio')
|
||||
return;
|
||||
|
||||
const { codec } = stream;
|
||||
const rtp = createAVFormatContext();
|
||||
rtp.create('rtp', rtp => {
|
||||
const prefix = Buffer.alloc(4);
|
||||
prefix.writeUInt8(RTSP_FRAME_MAGIC, 0);
|
||||
prefix.writeUInt8(stream.index, 1);
|
||||
prefix.writeUInt16BE(rtp.length, 2);
|
||||
|
||||
const chunk: StreamChunk = {
|
||||
chunks: [prefix, rtp],
|
||||
type: codec === 'hevc' ? 'h265' : codec,
|
||||
};
|
||||
|
||||
events.emit('rtsp', chunk);
|
||||
});
|
||||
rtp.newStream({
|
||||
formatContext,
|
||||
streamIndex: stream.index,
|
||||
});
|
||||
|
||||
pipelines.push({
|
||||
streamIndex: stream.index,
|
||||
writeFormatContext: rtp,
|
||||
});
|
||||
});
|
||||
|
||||
while (!killDeferred.finished) {
|
||||
using result = await formatContext.receiveFrame(pipelines);
|
||||
if (result)
|
||||
resetActivityTimer();
|
||||
if (killDeferred.finished)
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
kill(e);
|
||||
}
|
||||
finally {
|
||||
kill(new Error('rtsp read loop exited'));
|
||||
|
||||
await sleep(1000);
|
||||
await Promise.allSettled(pipelines.map(pipeline => pipeline.writeFormatContext.close()));
|
||||
await sleep(1000);
|
||||
await formatContext.close();
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
start: () => {
|
||||
startDeferred.resolve();
|
||||
},
|
||||
sdp: Promise.resolve(sdp),
|
||||
get isActive() { return !killDeferred.finished },
|
||||
kill(error?: Error) {
|
||||
kill(error);
|
||||
},
|
||||
killed: killDeferred.promise,
|
||||
resetActivityTimer,
|
||||
negotiateMediaStream: (requestMediaStream, inputVideoCodec, inputAudioCodec) => {
|
||||
return negotiateMediaStream(sdp, mediaStreamOptions, inputVideoCodec, inputAudioCodec, requestMediaStream);
|
||||
},
|
||||
emit(container: 'rtsp', chunk: StreamChunk) {
|
||||
events.emit(container, chunk);
|
||||
return this;
|
||||
},
|
||||
on(event: string, cb: any) {
|
||||
events.on(event, cb);
|
||||
return this;
|
||||
},
|
||||
once(event: any, cb: any) {
|
||||
events.once(event, cb);
|
||||
return this;
|
||||
},
|
||||
removeListener(event, cb) {
|
||||
events.removeListener(event, cb);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as libav from '@scrypted/libav';
|
||||
import path from 'path';
|
||||
|
||||
function getAddonInstallPath() {
|
||||
if (process.versions.electron)
|
||||
process.env.npm_config_runtime = 'electron';
|
||||
const binaryUrl = libav.getBinaryUrl();
|
||||
const u = new URL(binaryUrl);
|
||||
const withoutExtension = path.basename(u.pathname).replace(/\.tar.gz$/, '');
|
||||
return path.join(process.env.SCRYPTED_PLUGIN_VOLUME, libav.version, withoutExtension);
|
||||
}
|
||||
|
||||
export async function installLibavAddon(installOnly = false) {
|
||||
const nr = installOnly
|
||||
? null
|
||||
// @ts-expect-error
|
||||
: __non_webpack_require__;
|
||||
await libav.install(getAddonInstallPath(), nr);
|
||||
}
|
||||
@@ -14,9 +14,8 @@ import { parse as h264SpsParse } from "h264-sps-parser";
|
||||
import net, { AddressInfo } from 'net';
|
||||
import path from 'path';
|
||||
import { Duplex } from 'stream';
|
||||
import { ParserOptions, ParserSession, startParserSession } from './ffmpeg-session';
|
||||
import { ParserOptions, ParserSession, startParserSession } from './ffmpeg-rebroadcast';
|
||||
import { FileRtspServer } from './file-rtsp-server';
|
||||
import { startLibavSession } from './libav-parser';
|
||||
import { getUrlLocalAdresses } from './local-addresses';
|
||||
import { REBROADCAST_MIXIN_INTERFACE_TOKEN } from './rebroadcast-mixin-token';
|
||||
import { connectRFC4571Parser, startRFC4571Parser } from './rfc4571';
|
||||
@@ -33,8 +32,6 @@ const SCRYPTED_PARSER_TCP = 'Scrypted (TCP)';
|
||||
const SCRYPTED_PARSER_UDP = 'Scrypted (UDP)';
|
||||
const FFMPEG_PARSER_TCP = 'FFmpeg (TCP)';
|
||||
const FFMPEG_PARSER_UDP = 'FFmpeg (UDP)';
|
||||
const LIBAV_PARSER_TCP = 'Scrypted libav (TCP)';
|
||||
const LIBAV_PARSER_UDP = 'Scrypted libav (UDP)';
|
||||
const STRING_DEFAULT = 'Default';
|
||||
|
||||
interface PrebufferStreamChunk extends StreamChunk {
|
||||
@@ -62,6 +59,7 @@ class PrebufferSession {
|
||||
rtspPrebuffer: PrebufferStreamChunk[] = []
|
||||
parsers: { [container: string]: StreamParser };
|
||||
sdp: Promise<string>;
|
||||
usingScryptedParser = false;
|
||||
usingScryptedUdpParser = false;
|
||||
|
||||
mixinDevice: VideoCamera;
|
||||
@@ -238,8 +236,6 @@ class PrebufferSession {
|
||||
rtspParser = localStorage.getItem('defaultRtspParser');
|
||||
}
|
||||
switch (rtspParser) {
|
||||
case LIBAV_PARSER_TCP:
|
||||
case LIBAV_PARSER_UDP:
|
||||
case FFMPEG_PARSER_TCP:
|
||||
case FFMPEG_PARSER_UDP:
|
||||
case SCRYPTED_PARSER_TCP:
|
||||
@@ -396,8 +392,6 @@ class PrebufferSession {
|
||||
SCRYPTED_PARSER_UDP,
|
||||
FFMPEG_PARSER_TCP,
|
||||
FFMPEG_PARSER_UDP,
|
||||
LIBAV_PARSER_TCP,
|
||||
LIBAV_PARSER_UDP,
|
||||
],
|
||||
}
|
||||
);
|
||||
@@ -548,11 +542,12 @@ class PrebufferSession {
|
||||
// before launching the parser session, clear out the last detected codec.
|
||||
// an erroneous cached codec could cause ffmpeg to fail to start.
|
||||
this.storage.removeItem(this.lastDetectedAudioCodecKey);
|
||||
let usingScryptedParser = false;
|
||||
this.usingScryptedParser = false;
|
||||
|
||||
const h264Oddities = this.getLastH264Oddities();
|
||||
|
||||
if (isRfc4571) {
|
||||
usingScryptedParser = true;
|
||||
this.usingScryptedParser = true;
|
||||
this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser')
|
||||
const json = await mediaManager.convertMediaObjectToJSON<any>(mo, 'x-scrypted/x-rfc4571');
|
||||
let { url, sdp, mediaStreamOptions } = json;
|
||||
@@ -571,22 +566,21 @@ class PrebufferSession {
|
||||
sessionMso = ffmpegInput.mediaStreamOptions || this.advertisedMediaStreamOptions;
|
||||
|
||||
let { parser, isDefault } = this.getParser(sessionMso);
|
||||
usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP;
|
||||
const usingLibavParser = parser === LIBAV_PARSER_TCP || parser === LIBAV_PARSER_UDP;
|
||||
this.usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP;
|
||||
this.usingScryptedUdpParser = parser === SCRYPTED_PARSER_UDP;
|
||||
|
||||
// prefer ffmpeg if this is a prebuffered stream.
|
||||
if (isDefault
|
||||
&& usingScryptedParser
|
||||
&& this.usingScryptedParser
|
||||
&& h264Oddities
|
||||
&& !this.stopInactive
|
||||
&& sessionMso.tool !== 'scrypted') {
|
||||
this.console.warn('H264 oddities were detected in prebuffered video stream, the Default Scrypted RTSP Parser will not be used. Falling back to FFmpeg. This can be overriden by setting the RTSP Parser to Scrypted.');
|
||||
usingScryptedParser = false;
|
||||
this.usingScryptedParser = false;
|
||||
parser = FFMPEG_PARSER_TCP;
|
||||
}
|
||||
|
||||
if (usingScryptedParser) {
|
||||
if (this.usingScryptedParser) {
|
||||
const rtspParser = createRtspParser();
|
||||
rbo.parsers.rtsp = rtspParser;
|
||||
|
||||
@@ -596,16 +590,6 @@ class PrebufferSession {
|
||||
rtspRequestTimeout: 10000,
|
||||
});
|
||||
}
|
||||
else if (usingLibavParser) {
|
||||
const rtspParser = createRtspParser();
|
||||
rbo.parsers.rtsp = rtspParser;
|
||||
|
||||
session = await startLibavSession(this.console, ffmpegInput.url, ffmpegInput.mediaStreamOptions, {
|
||||
useUdp: parser === LIBAV_PARSER_UDP,
|
||||
audioSoftMuted,
|
||||
activityTimeout: 10000,
|
||||
});
|
||||
}
|
||||
else {
|
||||
let acodec: string[];
|
||||
|
||||
@@ -662,7 +646,7 @@ class PrebufferSession {
|
||||
console.error('rebroadcast error', e)
|
||||
});
|
||||
|
||||
if (usingScryptedParser && !isRfc4571) {
|
||||
if (this.usingScryptedParser && !isRfc4571) {
|
||||
// watch the stream for 10 seconds to see if an weird nalu is encountered.
|
||||
// if one is found and using scrypted parser as default, will need to restart rebroadcast to prevent
|
||||
// downstream issues.
|
||||
@@ -1022,6 +1006,10 @@ class PrebufferSession {
|
||||
const codecInfo = await this.parseCodecs(true);
|
||||
const mediaStreamOptions: ResponseMediaStreamOptions = session.negotiateMediaStream(options, codecInfo.inputVideoCodec, codecInfo.inputAudioCodec);
|
||||
let sdp = await this.sdp;
|
||||
if (!mediaStreamOptions.video?.h264Info && this.usingScryptedParser) {
|
||||
mediaStreamOptions.video ||= {};
|
||||
mediaStreamOptions.video.h264Info = this.getLastH264Probe();
|
||||
}
|
||||
|
||||
if (this.mixin.streamSettings.storageSettings.values.noAudio)
|
||||
mediaStreamOptions.audio = null;
|
||||
@@ -1635,8 +1623,6 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
||||
SCRYPTED_PARSER_UDP,
|
||||
FFMPEG_PARSER_TCP,
|
||||
FFMPEG_PARSER_UDP,
|
||||
LIBAV_PARSER_TCP,
|
||||
LIBAV_PARSER_UDP,
|
||||
],
|
||||
onPut: () => {
|
||||
this.log.a('Rebroadcast Plugin will restart momentarily.');
|
||||
@@ -1769,9 +1755,7 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
||||
this.setHasEnabledMixin(mixinDeviceState.id);
|
||||
|
||||
const { id } = mixinDeviceState;
|
||||
const forked = sdk.fork<RebroadcastPluginFork>({
|
||||
runtime: 'node',
|
||||
});
|
||||
const forked = sdk.fork<RebroadcastPluginFork>();
|
||||
const { worker } = forked;
|
||||
const result = await forked.result;
|
||||
const mixin = await result.newPrebufferMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { MediaStreamOptions, ResponseMediaStreamOptions } from "@scrypted/sdk";
|
||||
import { parse as spsParse } from "h264-sps-parser";
|
||||
import net from 'net';
|
||||
import { EventEmitter, Readable } from "stream";
|
||||
import { ParserSession, setupActivityTimer } from "./ffmpeg-session";
|
||||
import { ParserSession, setupActivityTimer } from "./ffmpeg-rebroadcast";
|
||||
import { getSpsResolution } from "./sps-resolution";
|
||||
|
||||
export function negotiateMediaStream(sdp: string, mediaStreamOptions: MediaStreamOptions, inputVideoCodec: string, inputAudioCodec: string, requestMediaStream: MediaStreamOptions) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { StreamChunk } from "@scrypted/common/src/stream-parser";
|
||||
import { ResponseMediaStreamOptions } from "@scrypted/sdk";
|
||||
import dgram from 'dgram';
|
||||
import { EventEmitter } from "stream";
|
||||
import { ParserSession, setupActivityTimer } from "./ffmpeg-session";
|
||||
import { ParserSession, setupActivityTimer } from "./ffmpeg-rebroadcast";
|
||||
import { negotiateMediaStream } from "./rfc4571";
|
||||
|
||||
export type RtspChannelCodecMapping = { [key: number]: string };
|
||||
|
||||
@@ -7,7 +7,7 @@ import { OnvifCameraAPI, OnvifEvent, connectCameraAPI } from './onvif-api';
|
||||
import { listenEvents } from './onvif-events';
|
||||
import { OnvifIntercom } from './onvif-intercom';
|
||||
import { DevInfo } from './probe';
|
||||
import { AIState, Enc, ReolinkCameraClient } from './reolink-api';
|
||||
import { AIState, Enc, isDeviceNvr, ReolinkCameraClient } from './reolink-api';
|
||||
|
||||
class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
|
||||
sirenTimeout: NodeJS.Timeout;
|
||||
@@ -103,7 +103,6 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
clientWithToken: ReolinkCameraClient;
|
||||
onvifClient: OnvifCameraAPI;
|
||||
onvifIntercom = new OnvifIntercom(this);
|
||||
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
|
||||
motionTimeout: NodeJS.Timeout;
|
||||
siren: ReolinkCameraSiren;
|
||||
floodlight: ReolinkCameraFloodlight;
|
||||
@@ -571,6 +570,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
async listenEvents() {
|
||||
let killed = false;
|
||||
const client = this.getClient();
|
||||
const deviceInfo = await client.getDeviceInfo();
|
||||
|
||||
// reolink ai might not trigger motion if objects are detected, weird.
|
||||
const startAI = async (ret: Destroyable, triggerMotion: () => void) => {
|
||||
@@ -620,7 +620,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (!hasSucceeded)
|
||||
if (!hasSucceeded && !isDeviceNvr(deviceInfo))
|
||||
return;
|
||||
ret.emit('error', e);
|
||||
}
|
||||
@@ -769,15 +769,6 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
}
|
||||
|
||||
async getConstructedVideoStreamOptions(): Promise<UrlMediaStreamOptions[]> {
|
||||
this.videoStreamOptions ||= this.getConstructedVideoStreamOptionsInternal().catch(e => {
|
||||
this.constructedVideoStreamOptions = undefined;
|
||||
throw e;
|
||||
});
|
||||
|
||||
return this.videoStreamOptions;
|
||||
}
|
||||
|
||||
async getConstructedVideoStreamOptionsInternal(): Promise<UrlMediaStreamOptions[]> {
|
||||
let deviceInfo: DevInfo;
|
||||
try {
|
||||
const client = this.getClient();
|
||||
|
||||
@@ -75,6 +75,8 @@ export interface PtzPreset {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const isDeviceNvr = (deviceInfo: DevInfo) => ['HOMEHUB', 'NVR', 'NVR_WIFI'].includes(deviceInfo.exactType);
|
||||
|
||||
export class ReolinkCameraClient {
|
||||
credential: AuthFetchCredentialState;
|
||||
parameters: Record<string, string>;
|
||||
@@ -320,7 +322,7 @@ export class ReolinkCameraClient {
|
||||
const deviceInfo: DevInfo = await response.body?.[0]?.value?.DevInfo;
|
||||
|
||||
// Will need to check if it's valid for NVR and NVR_WIFI
|
||||
if (!['HOMEHUB', 'NVR', 'NVR_WIFI'].includes(deviceInfo.exactType)) {
|
||||
if (!isDeviceNvr(deviceInfo)) {
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
|
||||
2
plugins/ring/.vscode/launch.json
vendored
2
plugins/ring/.vscode/launch.json
vendored
@@ -10,7 +10,7 @@
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"**/plugin-remote-worker.*",
|
||||
"**/plugin-console.*",
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
|
||||
2522
plugins/ring/package-lock.json
generated
2522
plugins/ring/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,7 @@
|
||||
"dependencies": {
|
||||
"@koush/ring-client-api": "file:../../external/ring-client-api",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@scrypted/sdk": "^0.3.61",
|
||||
"@types/node": "^18.15.11",
|
||||
"axios": "^1.3.5",
|
||||
"rxjs": "^7.8.0"
|
||||
@@ -45,5 +45,5 @@
|
||||
"got": "11.8.6",
|
||||
"socket.io-client": "^2.5.0"
|
||||
},
|
||||
"version": "0.0.144"
|
||||
"version": "0.0.145"
|
||||
}
|
||||
|
||||
@@ -365,9 +365,13 @@ export abstract class RtspSmartCamera extends RtspCamera {
|
||||
return this.constructedVideoStreamOptions;
|
||||
}
|
||||
|
||||
putSettingBase(key: string, value: SettingValue): Promise<void> {
|
||||
this.constructedVideoStreamOptions = undefined;
|
||||
return super.putSettingBase(key, value);
|
||||
async putSettingBase(key: string, value: SettingValue): Promise<void> {
|
||||
try {
|
||||
return await super.putSettingBase(key, value);
|
||||
}
|
||||
finally {
|
||||
this.constructedVideoStreamOptions = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
plugins/snapshot/package-lock.json
generated
4
plugins/snapshot/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/snapshot",
|
||||
"version": "0.2.58",
|
||||
"version": "0.2.59",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/snapshot",
|
||||
"version": "0.2.58",
|
||||
"version": "0.2.59",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.10.2",
|
||||
"sharp": "^0.33.5",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user