mirror of
https://github.com/koush/scrypted.git
synced 2026-02-08 08:19:56 +00:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aef218c653 | ||
|
|
aea24a84f0 | ||
|
|
bab3bef0d1 | ||
|
|
56bda46ae9 | ||
|
|
368b0fc26a | ||
|
|
fa50d6faab | ||
|
|
115d168cd3 | ||
|
|
7ab93e8883 | ||
|
|
e25cf860f0 | ||
|
|
b5b56d81a8 | ||
|
|
e7b6cb021c | ||
|
|
e96f374432 | ||
|
|
c4126d7569 | ||
|
|
074ba733a3 | ||
|
|
622f494703 | ||
|
|
28c8b97c26 | ||
|
|
c0af17b38d | ||
|
|
44a82a1afa | ||
|
|
c9b4c14e35 | ||
|
|
76534f1368 | ||
|
|
3f9a863961 | ||
|
|
83b0ebd5f0 | ||
|
|
b923a4ea27 | ||
|
|
941d213087 | ||
|
|
0e11b8b4c5 | ||
|
|
1be14878d1 | ||
|
|
3e323911c7 | ||
|
|
ca36ab2d2d | ||
|
|
a80e95912e | ||
|
|
4432d8dd67 | ||
|
|
2d83d3ba97 | ||
|
|
1078faef62 | ||
|
|
47a981e15a | ||
|
|
e253cab555 | ||
|
|
80124ca83b | ||
|
|
f883d8738c | ||
|
|
bdca3b545c | ||
|
|
3ba02c44ab | ||
|
|
fe4733bb97 | ||
|
|
7ff893fbd3 | ||
|
|
e79c544690 | ||
|
|
13bf44ce50 | ||
|
|
544531122d | ||
|
|
778f0b7ad1 | ||
|
|
35e8a86593 | ||
|
|
c370773af4 | ||
|
|
184f293b92 | ||
|
|
6e10172f7e | ||
|
|
c5ae2cd539 | ||
|
|
e40566e89c | ||
|
|
59ccd4e4d8 | ||
|
|
ae80eb7727 | ||
|
|
f054172dcf | ||
|
|
0d7fb9e13c | ||
|
|
a526816b07 | ||
|
|
563e16b08f | ||
|
|
fd56990d64 | ||
|
|
d7aaf57e8f | ||
|
|
a2d50d54d5 | ||
|
|
1f86745252 | ||
|
|
1f4343ba2e | ||
|
|
3ad311898f | ||
|
|
799e5b53c7 | ||
|
|
833e5b34ab | ||
|
|
c99ac28e89 | ||
|
|
841475cb97 | ||
|
|
4b03a3a458 | ||
|
|
d686dd815c | ||
|
|
e0386a8922 | ||
|
|
9ef3478c88 | ||
|
|
690d160f33 | ||
|
|
59ff987bca | ||
|
|
1669f17c96 | ||
|
|
b0bfd4e05e | ||
|
|
7152671913 | ||
|
|
537c178699 | ||
|
|
77ecee110b | ||
|
|
29b163a7d8 | ||
|
|
5d74e80e90 | ||
|
|
764b6441d5 | ||
|
|
e2c43cb4ff | ||
|
|
7b6d094e8c | ||
|
|
3dfb2db02a | ||
|
|
e5a549db6a | ||
|
|
d500c815fe | ||
|
|
5f71c59b5a | ||
|
|
27407942a5 | ||
|
|
11b6963744 | ||
|
|
b9ee8866f0 | ||
|
|
bc80d31eaa | ||
|
|
327688232c | ||
|
|
2883a4ce46 | ||
|
|
1ad2fb915d | ||
|
|
fb701a32b7 | ||
|
|
7a8c661bb3 | ||
|
|
54d72fb371 | ||
|
|
e48812cec7 | ||
|
|
6c2db072c4 | ||
|
|
4bf2c0b614 | ||
|
|
a93cdb0ae4 | ||
|
|
ff85b7abc6 | ||
|
|
46dfb8d98e | ||
|
|
5240200f0f | ||
|
|
3bcb94fc6b | ||
|
|
a596bc712c | ||
|
|
f6d2dc456e | ||
|
|
441cce239e | ||
|
|
3016df32d1 | ||
|
|
5bd8ed0b1a | ||
|
|
79286a5138 | ||
|
|
8874e01072 | ||
|
|
0223a9f0f6 | ||
|
|
890c2667d0 | ||
|
|
ca14764e17 | ||
|
|
1030d7d03c | ||
|
|
2d40320868 | ||
|
|
3e32c3d019 | ||
|
|
1f9fa3966f | ||
|
|
c2d86237d6 | ||
|
|
5cfcfafc00 |
12
.github/workflows/docker-common.yml
vendored
12
.github/workflows/docker-common.yml
vendored
@@ -10,7 +10,10 @@ jobs:
|
||||
# runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
NODE_VERSION: ["18", "20"]
|
||||
NODE_VERSION: [
|
||||
"18",
|
||||
# "20"
|
||||
]
|
||||
BASE: ["jammy"]
|
||||
FLAVOR: ["full", "lite", "thin"]
|
||||
steps:
|
||||
@@ -26,12 +29,6 @@ jobs:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
@@ -65,5 +62,6 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -52,12 +52,6 @@ jobs:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -38,3 +38,6 @@
|
||||
[submodule "plugins/wyze/docker-wyze-bridge"]
|
||||
path = plugins/wyze/docker-wyze-bridge
|
||||
url = ../../koush/docker-wyze-bridge.git
|
||||
[submodule "plugins/onvif/onvif"]
|
||||
path = plugins/onvif/onvif
|
||||
url = ../../koush/onvif.git
|
||||
|
||||
22
common/.vscode/launch.json
vendored
22
common/.vscode/launch.json
vendored
@@ -5,17 +5,19 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"name": "ts-node",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
"args": [
|
||||
"${workspaceFolder}/src/test.ts",
|
||||
],
|
||||
"program": "${workspaceFolder}/dist/common/src/test.js",
|
||||
"preLaunchTask": "npm: build",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/**/*.js"
|
||||
]
|
||||
}
|
||||
"runtimeArgs": [
|
||||
"-r",
|
||||
"ts-node/register"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"protocol": "inspector",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
]
|
||||
}
|
||||
510
common/package-lock.json
generated
510
common/package-lock.json
generated
@@ -1,22 +1,23 @@
|
||||
{
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../external/werift/packages/webrtc": {
|
||||
@@ -74,7 +75,7 @@
|
||||
},
|
||||
"../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.68",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -111,68 +112,103 @@
|
||||
},
|
||||
"../server": {
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.6.19",
|
||||
"version": "0.81.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
"@scrypted/types": "^0.2.63",
|
||||
"adm-zip": "^0.5.9",
|
||||
"axios": "^0.21.4",
|
||||
"body-parser": "^1.19.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"@scrypted/types": "^0.3.4",
|
||||
"adm-zip": "^0.5.10",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"debug": "^4.3.4",
|
||||
"engine.io": "^6.2.0",
|
||||
"engine.io": "^6.5.4",
|
||||
"express": "^4.18.2",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"follow-redirects": "^1.15.4",
|
||||
"http-auth": "^4.2.0",
|
||||
"ip": "^1.1.8",
|
||||
"level": "^6.0.1",
|
||||
"level": "^8.0.0",
|
||||
"linkfs": "^2.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"memfs": "^3.4.7",
|
||||
"memfs": "^4.6.0",
|
||||
"mime": "^3.0.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"nan": "^2.17.0",
|
||||
"nan": "^2.18.0",
|
||||
"node-dijkstra": "^2.5.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-gyp": "^8.4.1",
|
||||
"router": "^1.3.7",
|
||||
"semver": "^7.3.8",
|
||||
"node-gyp": "^10.0.1",
|
||||
"router": "^1.3.8",
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "^0.33.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tar": "^6.1.11",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.8.4",
|
||||
"whatwg-mimetype": "^2.3.0",
|
||||
"ws": "^8.9.0"
|
||||
"tar": "^6.2.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-serve": "bin/scrypted-serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.4.34",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/http-auth": "^4.1.1",
|
||||
"@types/ip": "^1.1.0",
|
||||
"@types/lodash": "^4.14.186",
|
||||
"@types/mime": "^3.0.1",
|
||||
"@types/mkdirp": "^1.0.2",
|
||||
"@types/node-dijkstra": "^2.5.3",
|
||||
"@types/node-forge": "^1.3.0",
|
||||
"@types/pem": "^1.9.6",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/source-map-support": "^0.5.6",
|
||||
"@types/tar": "^4.0.5",
|
||||
"@types/whatwg-mimetype": "^2.1.1",
|
||||
"@types/ws": "^7.4.7"
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/cookie-parser": "^1.4.6",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/follow-redirects": "^1.14.4",
|
||||
"@types/http-auth": "^4.1.4",
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node-dijkstra": "^2.5.6",
|
||||
"@types/node-forge": "^1.3.10",
|
||||
"@types/pem": "^1.14.4",
|
||||
"@types/semver": "^7.5.6",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/tar": "^6.1.10",
|
||||
"@types/whatwg-mimetype": "^3.0.2",
|
||||
"@types/ws": "^8.5.10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../sdk",
|
||||
"link": true
|
||||
@@ -181,12 +217,81 @@
|
||||
"resolved": "../server",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
|
||||
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
|
||||
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
|
||||
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz",
|
||||
@@ -231,6 +336,12 @@
|
||||
"node": ">=12.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
@@ -265,18 +376,73 @@
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
|
||||
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
|
||||
@@ -286,15 +452,55 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yerror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.0.1.tgz",
|
||||
"integrity": "sha512-0Bxo+NyeucjxhmPB5z3lmI/N/cOu8L1Q8JVta6/I5G6J/JhCSSPwk8qt9N4yOFSjwkvhDwzUSQglfBIAllvi1Q==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.2.1.tgz",
|
||||
"integrity": "sha512-WPZgybhCBzsMSSqGYBnj20NZo4FiFKQG0+i/21cYGVd4B7eYtvYDOpjk/0e8UM1eVHJ+4Ja6bZ7TAjHH6mk+ew==",
|
||||
"engines": {
|
||||
"node": ">=12.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
}
|
||||
},
|
||||
"@jridgewell/resolve-uri": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../sdk",
|
||||
"requires": {
|
||||
@@ -322,62 +528,117 @@
|
||||
"@scrypted/server": {
|
||||
"version": "file:../server",
|
||||
"requires": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
"@scrypted/types": "^0.2.63",
|
||||
"@types/adm-zip": "^0.4.34",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/http-auth": "^4.1.1",
|
||||
"@types/ip": "^1.1.0",
|
||||
"@types/lodash": "^4.14.186",
|
||||
"@types/mime": "^3.0.1",
|
||||
"@types/mkdirp": "^1.0.2",
|
||||
"@types/node-dijkstra": "^2.5.3",
|
||||
"@types/node-forge": "^1.3.0",
|
||||
"@types/pem": "^1.9.6",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/source-map-support": "^0.5.6",
|
||||
"@types/tar": "^4.0.5",
|
||||
"@types/whatwg-mimetype": "^2.1.1",
|
||||
"@types/ws": "^7.4.7",
|
||||
"adm-zip": "^0.5.9",
|
||||
"axios": "^0.21.4",
|
||||
"body-parser": "^1.19.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"@scrypted/types": "^0.3.4",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/cookie-parser": "^1.4.6",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/follow-redirects": "^1.14.4",
|
||||
"@types/http-auth": "^4.1.4",
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node-dijkstra": "^2.5.6",
|
||||
"@types/node-forge": "^1.3.10",
|
||||
"@types/pem": "^1.14.4",
|
||||
"@types/semver": "^7.5.6",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/tar": "^6.1.10",
|
||||
"@types/whatwg-mimetype": "^3.0.2",
|
||||
"@types/ws": "^8.5.10",
|
||||
"adm-zip": "^0.5.10",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"debug": "^4.3.4",
|
||||
"engine.io": "^6.2.0",
|
||||
"engine.io": "^6.5.4",
|
||||
"express": "^4.18.2",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"follow-redirects": "^1.15.4",
|
||||
"http-auth": "^4.2.0",
|
||||
"ip": "^1.1.8",
|
||||
"level": "^6.0.1",
|
||||
"level": "^8.0.0",
|
||||
"linkfs": "^2.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"memfs": "^3.4.7",
|
||||
"memfs": "^4.6.0",
|
||||
"mime": "^3.0.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"nan": "^2.17.0",
|
||||
"nan": "^2.18.0",
|
||||
"node-dijkstra": "^2.5.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-gyp": "^8.4.1",
|
||||
"node-gyp": "^10.0.1",
|
||||
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5",
|
||||
"router": "^1.3.7",
|
||||
"semver": "^7.3.8",
|
||||
"router": "^1.3.8",
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "^0.33.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tar": "^6.1.11",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.8.4",
|
||||
"whatwg-mimetype": "^2.3.0",
|
||||
"ws": "^8.9.0"
|
||||
"tar": "^6.2.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
|
||||
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
|
||||
"version": "20.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
|
||||
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
|
||||
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
|
||||
"dev": true
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"fetch-blob": {
|
||||
@@ -405,6 +666,12 @@
|
||||
"yerror": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
@@ -419,10 +686,43 @@
|
||||
"web-streams-polyfill": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
|
||||
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA=="
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true
|
||||
},
|
||||
"web-streams-polyfill": {
|
||||
"version": "3.2.0",
|
||||
@@ -430,9 +730,15 @@
|
||||
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA=="
|
||||
},
|
||||
"yerror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.0.1.tgz",
|
||||
"integrity": "sha512-0Bxo+NyeucjxhmPB5z3lmI/N/cOu8L1Q8JVta6/I5G6J/JhCSSPwk8qt9N4yOFSjwkvhDwzUSQglfBIAllvi1Q=="
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.2.1.tgz",
|
||||
"integrity": "sha512-WPZgybhCBzsMSSqGYBnj20NZo4FiFKQG0+i/21cYGVd4B7eYtvYDOpjk/0e8UM1eVHJ+4Ja6bZ7TAjHH6mk+ew=="
|
||||
},
|
||||
"yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/server": "file:../server",
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import sdk from "@scrypted/sdk";
|
||||
|
||||
const { systemManager, log } = sdk;
|
||||
|
||||
export async function alertRecommendedPlugins(plugins: { [pkg: string]: string }) {
|
||||
const pluginsComponent = await systemManager.getComponent('plugins');
|
||||
let recommended: any;
|
||||
try {
|
||||
recommended = JSON.parse(localStorage.getItem('alert-recommended'));
|
||||
}
|
||||
catch (e) {
|
||||
recommended = {};
|
||||
}
|
||||
for (const plugin of Object.keys(plugins)) {
|
||||
try {
|
||||
if (recommended[plugin])
|
||||
continue;
|
||||
|
||||
recommended[plugin] = true;
|
||||
localStorage.setItem('alert-recommended', JSON.stringify(recommended));
|
||||
const id = await pluginsComponent.getIdForPluginId(plugin);
|
||||
if (id)
|
||||
continue;
|
||||
const name = plugins[plugin];
|
||||
log.a(`Installation of the ${name} plugin is also recommended. origin:/#/component/plugin/install/${plugin}`)
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,18 +58,13 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
worker.worker.terminate();
|
||||
}
|
||||
|
||||
const smProxy = new SystemManagerImpl();
|
||||
smProxy.state = systemManager.getSystemState();
|
||||
const apiProxy = new PluginAPIProxy(sdk.pluginHostAPI);
|
||||
smProxy.api = apiProxy;
|
||||
|
||||
const allParams = Object.assign({}, params, {
|
||||
sdk,
|
||||
fs: require('realfs'),
|
||||
fetch,
|
||||
ScryptedDeviceBase,
|
||||
MixinDeviceBase,
|
||||
systemManager: smProxy,
|
||||
systemManager,
|
||||
deviceManager,
|
||||
endpointManager,
|
||||
mediaManager,
|
||||
@@ -104,7 +99,6 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
return {
|
||||
value,
|
||||
defaultExport,
|
||||
apiProxy,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
148
common/src/http-auth-fetch.ts
Normal file
148
common/src/http-auth-fetch.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, getHttpFetchParser, httpFetch } from '@scrypted/server/src/http-fetch-helpers';
|
||||
import crypto from 'crypto';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { BASIC, DIGEST, buildAuthorizationHeader, parseWWWAuthenticateHeader } from 'http-auth-utils/src/index';
|
||||
|
||||
export interface AuthFetchCredentialState {
|
||||
username: string;
|
||||
password: string;
|
||||
count?: number;
|
||||
digest?: ReturnType<typeof parseWWWAuthenticateHeader<typeof DIGEST>>;
|
||||
basic?: ReturnType<typeof parseWWWAuthenticateHeader<typeof BASIC>>;
|
||||
}
|
||||
|
||||
export interface AuthFetchOptions<T extends HttpFetchResponseType> extends HttpFetchOptions<T> {
|
||||
credential: AuthFetchCredentialState;
|
||||
}
|
||||
|
||||
function getAuth(options: AuthFetchOptions<any>, method: string) {
|
||||
if (!options.credential)
|
||||
return;
|
||||
const { digest, basic } = options.credential;
|
||||
if (digest) {
|
||||
options.credential.count ||= 0;
|
||||
++options.credential.count;
|
||||
const nc = ('00000000' + options.credential.count).slice(-8);
|
||||
const cnonce = crypto.randomBytes(24).toString('hex');
|
||||
const uri = new URL(options.url).pathname;
|
||||
|
||||
const response = DIGEST.computeHash({
|
||||
username: options.credential.username,
|
||||
password: options.credential.password,
|
||||
method,
|
||||
uri,
|
||||
nc,
|
||||
cnonce,
|
||||
algorithm: 'MD5',
|
||||
qop: digest.data.qop!,
|
||||
...digest.data,
|
||||
});
|
||||
|
||||
const header = buildAuthorizationHeader(DIGEST, {
|
||||
username: options.credential.username,
|
||||
uri,
|
||||
nc,
|
||||
cnonce,
|
||||
algorithm: digest.data.algorithm!,
|
||||
qop: digest.data.qop!,
|
||||
response,
|
||||
...digest.data,
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
else if (basic) {
|
||||
const header = buildAuthorizationHeader(BASIC, {
|
||||
username: options.credential.username,
|
||||
password: options.credential.password,
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
export async function authHttpFetch<T extends HttpFetchResponseType>(options: AuthFetchOptions<T>): ReturnType<typeof httpFetch<AuthFetchOptions<T>>> {
|
||||
const method = options.method || 'GET';
|
||||
const headers = new Headers(options.headers);
|
||||
options.headers = headers;
|
||||
|
||||
const initialHeader = getAuth(options, method);
|
||||
if (initialHeader)
|
||||
headers.set('Authorization', initialHeader);
|
||||
|
||||
const initialResponse = await httpFetch({
|
||||
...options,
|
||||
ignoreStatusCode: true,
|
||||
responseType: 'readable',
|
||||
});
|
||||
|
||||
const parser = getHttpFetchParser(options.responseType);
|
||||
|
||||
if (initialResponse.statusCode !== 401 || !options.credential) {
|
||||
if (!options?.ignoreStatusCode)
|
||||
checkStatus(initialResponse.statusCode);
|
||||
return {
|
||||
...initialResponse,
|
||||
body: await parser.parse(initialResponse.body),
|
||||
};
|
||||
}
|
||||
|
||||
let authenticateHeaders: string | string[] = initialResponse.headers['www-authenticate'];
|
||||
if (!authenticateHeaders)
|
||||
throw new Error('Did not find WWW-Authenticate header.');
|
||||
|
||||
|
||||
if (typeof authenticateHeaders === 'string')
|
||||
authenticateHeaders = [authenticateHeaders];
|
||||
|
||||
const parsedHeaders = authenticateHeaders.map(h => parseWWWAuthenticateHeader(h));
|
||||
|
||||
const digest = parsedHeaders.find(p => p.type === 'Digest') as ReturnType<typeof parseWWWAuthenticateHeader<typeof DIGEST>>;
|
||||
const basic = parsedHeaders.find(p => p.type === 'Basic') as ReturnType<typeof parseWWWAuthenticateHeader<typeof BASIC>>;
|
||||
|
||||
options.credential.digest = digest;
|
||||
options.credential.basic = basic;
|
||||
|
||||
if (!digest && !basic)
|
||||
throw new Error(`Unknown WWW-Authenticate type: ${parsedHeaders[0]?.type}`);
|
||||
|
||||
const header = getAuth(options, method);
|
||||
if (header)
|
||||
headers.set('Authorization', header);
|
||||
|
||||
return httpFetch(options);
|
||||
}
|
||||
|
||||
function ensureType<T>(v: T) {
|
||||
}
|
||||
|
||||
async function test() {
|
||||
const a = await authHttpFetch({
|
||||
credential: undefined,
|
||||
url: 'http://example.com',
|
||||
});
|
||||
|
||||
ensureType<Buffer>(a.body);
|
||||
|
||||
const b = await authHttpFetch({
|
||||
credential: undefined,
|
||||
url: 'http://example.com',
|
||||
responseType: 'json',
|
||||
});
|
||||
ensureType<any>(b.body);
|
||||
|
||||
const c = await authHttpFetch({
|
||||
credential: undefined,
|
||||
url: 'http://example.com',
|
||||
responseType: 'readable',
|
||||
});
|
||||
ensureType<IncomingMessage>(c.body);
|
||||
|
||||
const d = await authHttpFetch({
|
||||
credential: undefined,
|
||||
url: 'http://example.com',
|
||||
responseType: 'buffer',
|
||||
});
|
||||
ensureType<Buffer>(d.body);
|
||||
}
|
||||
|
||||
2
external/axios-digest-auth
vendored
2
external/axios-digest-auth
vendored
Submodule external/axios-digest-auth updated: e1735135be...1180ec6d53
2
external/werift
vendored
2
external/werift
vendored
Submodule external/werift updated: 25be131232...a0070297a4
@@ -1,6 +1,6 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "18-jammy-full.s6-v0.68.0"
|
||||
version: "18-jammy-full.s6-v0.80.0"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
@@ -29,9 +29,9 @@ environment:
|
||||
SCRYPTED_ADMIN_USERNAME: "homeassistant"
|
||||
SCRYPTED_INSTALL_ENVIRONMENT: "ha"
|
||||
backup_exclude:
|
||||
- '/server/**'
|
||||
- '/data/scrypted_nvr/**'
|
||||
- '/data/scrypted_data/plugins/**'
|
||||
- '*/server/**'
|
||||
- '*/scrypted_nvr/**'
|
||||
- '*/scrypted_data/plugins/**'
|
||||
map:
|
||||
- config:rw
|
||||
- media:rw
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG BASE="18-jammy-full"
|
||||
FROM koush/scrypted-common:${BASE}
|
||||
FROM ghcr.io/koush/scrypted-common:${BASE}
|
||||
|
||||
WORKDIR /
|
||||
# cache bust
|
||||
@@ -14,4 +14,8 @@ WORKDIR /server
|
||||
# https://github.com/nodejs/node/issues/41145#issuecomment-992948130
|
||||
ENV NODE_OPTIONS="--dns-result-order=ipv4first"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20240103"
|
||||
|
||||
CMD npm --prefix /server exec scrypted-serve
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG BASE="16-jammy"
|
||||
FROM koush/scrypted-common:${BASE}
|
||||
FROM ghcr.io/koush/scrypted-common:${BASE}
|
||||
|
||||
WORKDIR /
|
||||
RUN git clone --depth=1 https://github.com/koush/scrypted
|
||||
|
||||
@@ -25,10 +25,14 @@ RUN apt-get update && apt-get -y install \
|
||||
apt-get -y upgrade
|
||||
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get install -y ca-certificates curl gnupg
|
||||
RUN mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
# python native
|
||||
RUN echo "Installing python."
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
@@ -38,44 +42,25 @@ RUN apt-get -y install \
|
||||
|
||||
# these are necessary for pillow-simd, additional on disk size is small
|
||||
# but could consider removing this.
|
||||
RUN echo "Installing pillow-simd dependencies."
|
||||
RUN apt-get -y install \
|
||||
libjpeg-dev zlib1g-dev
|
||||
|
||||
# plugins support fallback to pillow, but vips is faster.
|
||||
RUN apt-get -y install \
|
||||
libvips
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN echo "Installing gstreamer."
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
|
||||
gstreamer1.0-vaapi
|
||||
|
||||
# python3 gstreamer bindings
|
||||
RUN echo "Installing gstreamer bindings."
|
||||
RUN apt-get -y install \
|
||||
python3-gst-1.0
|
||||
|
||||
# armv7l does not have wheels for any of these
|
||||
# and compile times would forever, if it works at all.
|
||||
# furthermore, it's possible to run 32bit docker on 64bit arm,
|
||||
# which causes weird behavior in python which looks at the arch version
|
||||
# which still reports 64bit, even if running in 32bit docker.
|
||||
# this scenario is not supported and will be reported at runtime.
|
||||
# this bit is not necessary on amd64, but leaving it for consistency.
|
||||
RUN apt-get -y install \
|
||||
python3-matplotlib \
|
||||
python3-numpy \
|
||||
python3-opencv \
|
||||
python3-pil \
|
||||
python3-skimage
|
||||
|
||||
# allow pip to install to system
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
|
||||
# pyvips is broken on x86 due to mismatch ffi
|
||||
# https://stackoverflow.com/questions/62658237/it-seems-that-the-version-of-the-libffi-library-seen-at-runtime-is-different-fro
|
||||
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
################################################################
|
||||
@@ -87,20 +72,11 @@ RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
FROM header as base
|
||||
|
||||
# intel opencl gpu for openvino
|
||||
RUN bash -c "if [ \"$(uname -m)\" == \"x86_64\" ]; \
|
||||
then \
|
||||
apt-get update && apt-get install -y gpg-agent && \
|
||||
rm -f /usr/share/keyrings/intel-graphics.gpg && \
|
||||
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg && \
|
||||
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list && \
|
||||
apt-get -y update && \
|
||||
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free && \
|
||||
apt-get -y dist-upgrade; \
|
||||
fi"
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa && \
|
||||
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
|
||||
apt-get -y install \
|
||||
python3.9 \
|
||||
python3.9-dev \
|
||||
@@ -110,7 +86,6 @@ RUN add-apt-repository ppa:deadsnakes/ppa && \
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
|
||||
RUN python3.9 -m pip install --upgrade pip
|
||||
RUN python3.9 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3.9 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
# Coral Edge TPU
|
||||
@@ -127,9 +102,6 @@ ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
RUN test -f "/usr/bin/ffmpeg"
|
||||
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20230727"
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="full"
|
||||
|
||||
################################################################
|
||||
|
||||
@@ -17,7 +17,10 @@ RUN apt-get update && apt-get -y install \
|
||||
apt-get -y upgrade
|
||||
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get install -y ca-certificates curl gnupg
|
||||
RUN mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
# python native
|
||||
@@ -41,7 +44,4 @@ ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
RUN test -f "/usr/bin/ffmpeg"
|
||||
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20230727"
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="lite"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM koush/scrypted-common
|
||||
FROM ghcr.io/koush/scrypted-common
|
||||
|
||||
WORKDIR /
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM koush/18-jammy-full.s6
|
||||
FROM ghcr.io/koush/scrypted:18-jammy-full.s6
|
||||
|
||||
WORKDIR /
|
||||
|
||||
@@ -12,11 +12,3 @@ ENV PATH=$CONDA_DIR/bin:$PATH
|
||||
RUN conda install -c conda-forge cudatoolkit=11.2.2 cudnn=8.1.0
|
||||
ENV CONDA_PREFIX=/opt/conda
|
||||
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CONDA_PREFIX/lib/
|
||||
|
||||
# this is a copy pasta, seems to need a reinstall.
|
||||
# python pip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
# pyvips is broken on x86 due to mismatch ffi
|
||||
# https://stackoverflow.com/questions/62658237/it-seems-that-the-version-of-the-libffi-library-seen-at-runtime-is-different-fro
|
||||
RUN python3 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG BASE="18-jammy-full"
|
||||
FROM koush/scrypted-common:${BASE}
|
||||
FROM ghcr.io/koush/scrypted-common:${BASE}
|
||||
|
||||
# avahi advertiser support
|
||||
RUN apt-get update && apt-get -y install \
|
||||
@@ -44,4 +44,8 @@ WORKDIR /server
|
||||
# https://github.com/nodejs/node/issues/41145#issuecomment-992948130
|
||||
ENV NODE_OPTIONS="--dns-result-order=ipv4first"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20240103"
|
||||
|
||||
CMD npm --prefix /server exec scrypted-serve
|
||||
|
||||
@@ -9,7 +9,7 @@ RUN apt-get -y update && \
|
||||
|
||||
# switch to nvm?
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && apt-get update && apt-get install -y nodejs
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
@@ -19,7 +19,4 @@ ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
RUN test -f "/usr/bin/ffmpeg"
|
||||
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20230727"
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="thin"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
./template/generate-dockerfile.sh
|
||||
|
||||
docker build -t koush/scrypted-common -f Dockerfile.common . && \
|
||||
docker build -t koush/scrypted -f Dockerfile.local .
|
||||
docker build -t ghcr.io/koush/scrypted-common -f Dockerfile.full . && \
|
||||
docker build -t ghcr.io/koush/scrypted -f Dockerfile.local .
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
./docker-build.sh
|
||||
|
||||
docker build -t koush/scrypted:18-jammy-full.nvidia -f Dockerfile.nvidia
|
||||
docker build -t ghcr.io/koush/scrypted:18-jammy-full.nvidia -f Dockerfile.nvidia .
|
||||
@@ -11,8 +11,8 @@ echo $BASE
|
||||
SUPERVISOR=.s6
|
||||
SUPERVISOR_BASE=$BASE$SUPERVISOR
|
||||
|
||||
docker build -t koush/scrypted-common:$BASE -f Dockerfile.$FLAVOR \
|
||||
docker build -t ghcr.io/koush/scrypted-common:$BASE -f Dockerfile.$FLAVOR \
|
||||
--build-arg NODE_VERSION=$NODE_VERSION --build-arg BASE=$IMAGE_BASE . && \
|
||||
\
|
||||
docker build -t koush/scrypted:$SUPERVISOR_BASE -f Dockerfile$SUPERVISOR \
|
||||
docker build -t ghcr.io/koush/scrypted:$SUPERVISOR_BASE -f Dockerfile$SUPERVISOR \
|
||||
--build-arg BASE=$BASE --build-arg SCRYPTED_INSTALL_VERSION=$SCRYPTED_INSTALL_VERSION .
|
||||
|
||||
@@ -90,7 +90,7 @@ services:
|
||||
container_name: scrypted
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
image: koush/scrypted
|
||||
image: ghcr.io/koush/scrypted
|
||||
|
||||
# logging is noisy and will unnecessarily wear on flash storage.
|
||||
# scrypted has per device in memory logging that is preferred.
|
||||
|
||||
16
install/docker/install-intel-graphics.sh
Normal file
16
install/docker/install-intel-graphics.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
if [ "$(uname -m)" = "x86_64" ]
|
||||
then
|
||||
echo "Installing Intel graphics packages."
|
||||
apt-get update && apt-get install -y gpg-agent &&
|
||||
rm -f /usr/share/keyrings/intel-graphics.gpg &&
|
||||
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
|
||||
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list &&
|
||||
apt-get -y update &&
|
||||
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free &&
|
||||
apt-get -y dist-upgrade;
|
||||
exit $?
|
||||
else
|
||||
echo "Intel graphics will not be installed on this architecture."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -6,19 +6,6 @@ then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$SERVICE_USER" == "root" ]
|
||||
then
|
||||
echo "Scrypted SERVICE_USER root is not allowed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USER_HOME=$(eval echo ~$SERVICE_USER)
|
||||
SCRYPTED_HOME=$USER_HOME/.scrypted
|
||||
mkdir -p $SCRYPTED_HOME
|
||||
|
||||
set -e
|
||||
cd $SCRYPTED_HOME
|
||||
|
||||
function readyn() {
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
@@ -30,6 +17,22 @@ function readyn() {
|
||||
done
|
||||
}
|
||||
|
||||
if [ "$SERVICE_USER" == "root" ]
|
||||
then
|
||||
readyn "Scrypted will store its files in the root user home directory. Running as a non-root user is recommended. Are you sure?"
|
||||
if [ "$yn" == "n" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
USER_HOME=$(eval echo ~$SERVICE_USER)
|
||||
SCRYPTED_HOME=$USER_HOME/.scrypted
|
||||
mkdir -p $SCRYPTED_HOME
|
||||
|
||||
set -e
|
||||
cd $SCRYPTED_HOME
|
||||
|
||||
readyn "Install Docker?"
|
||||
|
||||
if [ "$yn" == "y" ]
|
||||
|
||||
@@ -4,20 +4,11 @@
|
||||
FROM header as base
|
||||
|
||||
# intel opencl gpu for openvino
|
||||
RUN bash -c "if [ \"$(uname -m)\" == \"x86_64\" ]; \
|
||||
then \
|
||||
apt-get update && apt-get install -y gpg-agent && \
|
||||
rm -f /usr/share/keyrings/intel-graphics.gpg && \
|
||||
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg && \
|
||||
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list && \
|
||||
apt-get -y update && \
|
||||
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free && \
|
||||
apt-get -y dist-upgrade; \
|
||||
fi"
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa && \
|
||||
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
|
||||
apt-get -y install \
|
||||
python3.9 \
|
||||
python3.9-dev \
|
||||
@@ -27,7 +18,6 @@ RUN add-apt-repository ppa:deadsnakes/ppa && \
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
|
||||
RUN python3.9 -m pip install --upgrade pip
|
||||
RUN python3.9 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3.9 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
# Coral Edge TPU
|
||||
@@ -44,9 +34,6 @@ ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
RUN test -f "/usr/bin/ffmpeg"
|
||||
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20230727"
|
||||
ENV SCRYPTED_DOCKER_FLAVOR="full"
|
||||
|
||||
################################################################
|
||||
|
||||
@@ -22,10 +22,14 @@ RUN apt-get update && apt-get -y install \
|
||||
apt-get -y upgrade
|
||||
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get install -y ca-certificates curl gnupg
|
||||
RUN mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
# python native
|
||||
RUN echo "Installing python."
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
@@ -35,44 +39,25 @@ RUN apt-get -y install \
|
||||
|
||||
# these are necessary for pillow-simd, additional on disk size is small
|
||||
# but could consider removing this.
|
||||
RUN echo "Installing pillow-simd dependencies."
|
||||
RUN apt-get -y install \
|
||||
libjpeg-dev zlib1g-dev
|
||||
|
||||
# plugins support fallback to pillow, but vips is faster.
|
||||
RUN apt-get -y install \
|
||||
libvips
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN echo "Installing gstreamer."
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
|
||||
gstreamer1.0-vaapi
|
||||
|
||||
# python3 gstreamer bindings
|
||||
RUN echo "Installing gstreamer bindings."
|
||||
RUN apt-get -y install \
|
||||
python3-gst-1.0
|
||||
|
||||
# armv7l does not have wheels for any of these
|
||||
# and compile times would forever, if it works at all.
|
||||
# furthermore, it's possible to run 32bit docker on 64bit arm,
|
||||
# which causes weird behavior in python which looks at the arch version
|
||||
# which still reports 64bit, even if running in 32bit docker.
|
||||
# this scenario is not supported and will be reported at runtime.
|
||||
# this bit is not necessary on amd64, but leaving it for consistency.
|
||||
RUN apt-get -y install \
|
||||
python3-matplotlib \
|
||||
python3-numpy \
|
||||
python3-opencv \
|
||||
python3-pil \
|
||||
python3-skimage
|
||||
|
||||
# allow pip to install to system
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
|
||||
# pyvips is broken on x86 due to mismatch ffi
|
||||
# https://stackoverflow.com/questions/62658237/it-seems-that-the-version-of-the-libffi-library-seen-at-runtime-is-different-fro
|
||||
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
################################################################
|
||||
|
||||
@@ -12,6 +12,26 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function readyn() {
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
[Yy]* ) break;;
|
||||
[Nn]* ) break;;
|
||||
* ) echo "Please answer yes or no. (y/n)";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
if [ "$SERVICE_USER" = "root" ] && [ -z "$SERVICE_USER_ROOT" ]
|
||||
then
|
||||
readyn "Scrypted will store its files in the root user home directory. Running as a non-root user is recommended. Are you sure?"
|
||||
if [ "$yn" == "n" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Stopping existing service if it is running..."
|
||||
systemctl stop scrypted.service
|
||||
|
||||
@@ -49,6 +69,14 @@ ENV() {
|
||||
}
|
||||
|
||||
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/template/Dockerfile.full.header)
|
||||
if [ -z "$SCRYPTED_INSTALL_ENVIRONMENT" ]
|
||||
then
|
||||
SCRYPTED_INSTALL_ENVIRONMENT=local
|
||||
fi
|
||||
if [ "$SCRYPTED_INSTALL_ENVIRONMENT" = "lxc" ]
|
||||
then
|
||||
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/template/Dockerfile.full.footer)
|
||||
fi
|
||||
|
||||
if [ -z "$SERVICE_USER" ]
|
||||
then
|
||||
@@ -56,12 +84,6 @@ then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$SERVICE_USER" == "root" ]
|
||||
then
|
||||
echo "Scrypted SERVICE_USER root is not allowed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# this is not RUN as we do not care about the result
|
||||
USER_HOME=$(eval echo ~$SERVICE_USER)
|
||||
echo "Setting permissions on $USER_HOME/.scrypted"
|
||||
@@ -84,6 +106,7 @@ ExecStart=/usr/bin/npx -y scrypted serve
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
Environment="NODE_OPTIONS=$NODE_OPTIONS"
|
||||
Environment="SCRYPTED_INSTALL_ENVIRONMENT=$SCRYPTED_INSTALL_ENVIRONMENT"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
75
install/local/install-scrypted-proxmox.sh
Normal file
75
install/local/install-scrypted-proxmox.sh
Normal file
@@ -0,0 +1,75 @@
|
||||
function readyn() {
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
[Yy]* ) break;;
|
||||
[Nn]* ) break;;
|
||||
* ) echo "Please answer yes or no. (y/n)";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.80.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
VMID=10443
|
||||
fi
|
||||
|
||||
echo "Downloading scrypted container backup."
|
||||
if [ ! -f "$SCRYPTED_TAR_ZST" ]
|
||||
then
|
||||
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
|
||||
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
|
||||
fi
|
||||
|
||||
echo "Checking for existing container."
|
||||
pct config $VMID
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "Existing container $VMID found. Run this script with --force to overwrite the existing container."
|
||||
echo "This will wipe all existing data. Clone the existing container to retain the data, then reassign the owner of the scrypted volume after installation is complete."
|
||||
echo ""
|
||||
echo "bash $0 --force"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
pct restore $VMID $SCRYPTED_TAR_ZST $@
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "pct restore failed"
|
||||
echo ""
|
||||
echo "This may be caused by the server's 'local' storage not supporting containers."
|
||||
echo "Try running this script again with a different storage device (local-lvm, local-zfs). For example:"
|
||||
echo ""
|
||||
echo "bash $0 --storage local-lvm"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONF=/etc/pve/lxc/$VMID.conf
|
||||
if [ -f "$CONF" ]
|
||||
then
|
||||
echo "onboot: 1" >> $CONF
|
||||
else
|
||||
echo "$CONF not found? Start on boot must be enabled manually."
|
||||
fi
|
||||
|
||||
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
|
||||
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"renderD128\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"card0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
fi
|
||||
|
||||
echo "Scrypted setup is complete and the container resources can be started."
|
||||
echo "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."
|
||||
177
plugins/amcrest/package-lock.json
generated
177
plugins/amcrest/package-lock.json
generated
@@ -1,22 +1,19 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.130",
|
||||
"version": "0.0.131",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.130",
|
||||
"version": "0.0.131",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/multiparty": "^0.0.33",
|
||||
"multiparty": "^4.2.3"
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.18"
|
||||
"@types/node": "^20.10.8"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -28,15 +25,16 @@
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -71,15 +69,6 @@
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.6.tgz",
|
||||
"integrity": "sha512-e/XKs7/BYpPQkces0Cm4dUmhT9hR0rjvnNZAVRyRnNWdQ8cyCMFWS9HIrMWOdzAocKDNBXi1vKjJ8CywrW5xgQ==",
|
||||
"dependencies": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
@@ -88,150 +77,20 @@
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/multiparty": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-0.0.33.tgz",
|
||||
"integrity": "sha512-Il6cJUpSqgojT7NxbVJUvXkCblm50/yEJYtblISDsNIeNYf4yMAhdizzidUk6h8pJ8yhwK/3Fkb+3Dwcgtwl8w==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.16.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz",
|
||||
"integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw=="
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"version": "20.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
|
||||
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
|
||||
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
|
||||
"dependencies": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/multiparty": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.3.tgz",
|
||||
"integrity": "sha512-Ak6EUJZuhGS8hJ3c2fY6UW5MbkGUPMBEGd13djUzoY/BHqV/gTuFWtC6IuVA7A2+v3yjBS6c4or50xhzTQZImQ==",
|
||||
"dependencies": {
|
||||
"http-errors": "~1.8.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.130",
|
||||
"version": "0.0.131",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -35,13 +35,10 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/multiparty": "^0.0.33",
|
||||
"multiparty": "^4.2.3"
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.18"
|
||||
"@types/node": "^20.10.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import { AuthFetchCredentialState, AuthFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { Readable } from 'stream';
|
||||
import https from 'https';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { amcrestHttpsAgent, getDeviceInfo } from './probe';
|
||||
import { HttpFetchOptions, HttpFetchResponseType } from '../../../server/src/http-fetch-helpers';
|
||||
import { getDeviceInfo } from './probe';
|
||||
|
||||
export enum AmcrestEvent {
|
||||
MotionStart = "Code=VideoMotion;action=Start",
|
||||
@@ -22,35 +21,44 @@ export enum AmcrestEvent {
|
||||
DahuaTalkPulse = "Code=_CallNoAnswer_;action=Pulse",
|
||||
}
|
||||
|
||||
|
||||
export class AmcrestCameraClient {
|
||||
digestAuth: AxiosDigestAuth;
|
||||
credential: AuthFetchCredentialState;
|
||||
|
||||
constructor(public ip: string, username: string, password: string, public console?: Console) {
|
||||
this.digestAuth = new AxiosDigestAuth({
|
||||
this.credential = {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async request<T extends HttpFetchResponseType>(urlOrOptions: string | URL | HttpFetchOptions<T>, body?: Readable) {
|
||||
const options: AuthFetchOptions<T> = {
|
||||
...typeof urlOrOptions !== 'string' && !(urlOrOptions instanceof URL) ? urlOrOptions : {
|
||||
url: urlOrOptions,
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
credential: this.credential,
|
||||
body,
|
||||
};
|
||||
|
||||
const response = await authHttpFetch(options);
|
||||
return response;
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'text',
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/cgi-bin/magicBox.cgi?action=reboot`,
|
||||
responseType: 'text',
|
||||
});
|
||||
return response.data as string;
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async checkTwoWayAudio() {
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'text',
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/cgi-bin/devAudioOutput.cgi?action=getCollect`,
|
||||
responseType: 'text',
|
||||
});
|
||||
return (response.data as string).includes('result=1');
|
||||
return response.body.includes('result=1');
|
||||
}
|
||||
|
||||
// appAutoStart=true
|
||||
@@ -61,33 +69,27 @@ export class AmcrestCameraClient {
|
||||
// updateSerial=IPC-AW46WN-S2
|
||||
// updateSerialCloudUpgrade=IPC-AW46WN-.....
|
||||
async getDeviceInfo() {
|
||||
return getDeviceInfo(this.digestAuth, this.ip);
|
||||
return getDeviceInfo(this.credential, this.ip);
|
||||
}
|
||||
|
||||
async jpegSnapshot(): Promise<Buffer> {
|
||||
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/cgi-bin/snapshot.cgi`,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
return Buffer.from(response.data);
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async listenEvents() {
|
||||
const url = `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[All]`;
|
||||
console.log('preparing event listener', url);
|
||||
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'stream',
|
||||
const response = await this.request({
|
||||
url,
|
||||
responseType: 'readable',
|
||||
});
|
||||
const stream = response.data as IncomingMessage;
|
||||
const stream = response.body;
|
||||
stream.socket.setKeepAlive(true);
|
||||
|
||||
stream.on('data', (buffer: Buffer) => {
|
||||
@@ -118,12 +120,12 @@ export class AmcrestCameraClient {
|
||||
async enableContinousRecording(channel: number) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const url = `http://${this.ip}/cgi-bin/configManager.cgi?action=setConfig&Record[${channel - 1}].TimeSection[${i}][0]=1 00:00:00-23:59:59`;
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
method: "POST",
|
||||
const response = await this.request({
|
||||
url,
|
||||
});
|
||||
this.console.log(response.data);
|
||||
method: 'POST',
|
||||
responseType: 'text',
|
||||
},);
|
||||
this.console.log(response.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { PassThrough, Readable, Stream } from "stream";
|
||||
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
|
||||
import { RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
|
||||
import { AmcrestCameraClient, AmcrestEvent } from "./amcrest-api";
|
||||
import { amcrestHttpsAgent } from './probe';
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
@@ -94,12 +93,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
|
||||
for (const element of deviceParameters) {
|
||||
try {
|
||||
const response = await this.getClient().digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
url: `http://${this.getHttpAddress()}/cgi-bin/magicBox.cgi?action=${element.action}`
|
||||
const response = await this.getClient().request({
|
||||
url: `http://${this.getHttpAddress()}/cgi-bin/magicBox.cgi?action=${element.action}`,
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
const result = String(response.data).replace(element.replace, "").trim();
|
||||
const result = String(response.body).replace(element.replace, "").trim();
|
||||
deviceInfo[element.parameter] = result;
|
||||
}
|
||||
catch (e) {
|
||||
@@ -147,11 +145,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
if (![...params.keys()].length)
|
||||
return;
|
||||
|
||||
const response = await this.getClient().digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${params}`
|
||||
const response = await this.getClient().request({
|
||||
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${params}`,
|
||||
responseType: 'text',
|
||||
});
|
||||
this.console.log('reconfigure result', response.data);
|
||||
this.console.log('reconfigure result', response.body);
|
||||
}
|
||||
|
||||
getClient() {
|
||||
@@ -309,9 +307,6 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async takeSmartCameraPicture(option?: PictureOptions): Promise<MediaObject> {
|
||||
return this.createMediaObject(await this.getClient().jpegSnapshot(), 'image/jpeg');
|
||||
}
|
||||
@@ -334,7 +329,6 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
return this.storage.getItem('rtspChannel');
|
||||
}
|
||||
|
||||
|
||||
createRtspMediaStreamOptions(url: string, index: number) {
|
||||
const ret = super.createRtspMediaStreamOptions(url, index);
|
||||
ret.tool = 'scrypted';
|
||||
@@ -348,12 +342,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
this.videoStreamOptions = (async () => {
|
||||
let mas: string;
|
||||
try {
|
||||
const response = await client.digestAuth.request({
|
||||
const response = await client.request({
|
||||
url: `http://${this.getHttpAddress()}/cgi-bin/magicBox.cgi?action=getProductDefinition&name=MaxExtraStream`,
|
||||
responseType: 'text',
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
})
|
||||
mas = response.data.split('=')[1].trim();
|
||||
mas = response.body.split('=')[1].trim();
|
||||
this.storage.setItem('maxExtraStreams', mas.toString());
|
||||
}
|
||||
catch (e) {
|
||||
@@ -366,18 +359,16 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
const vsos = [...Array(maxExtraStreams + 1).keys()].map(subtype => this.createRtspMediaStreamOptions(`rtsp://${this.getRtspAddress()}/cam/realmonitor?channel=${channel}&subtype=${subtype}`, subtype));
|
||||
|
||||
try {
|
||||
const capResponse = await client.digestAuth.request({
|
||||
const capResponse = await client.request({
|
||||
url: `http://${this.getHttpAddress()}/cgi-bin/encode.cgi?action=getConfigCaps&channel=0`,
|
||||
responseType: 'text',
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
});
|
||||
this.console.log(capResponse.data);
|
||||
const encodeResponse = await client.digestAuth.request({
|
||||
this.console.log(capResponse.body);
|
||||
const encodeResponse = await client.request({
|
||||
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=getConfig&name=Encode`,
|
||||
responseType: 'text',
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
});
|
||||
this.console.log(encodeResponse.data);
|
||||
this.console.log(encodeResponse.body);
|
||||
|
||||
for (let i = 0; i < vsos.length; i++) {
|
||||
const vso = vsos[i];
|
||||
@@ -392,9 +383,9 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
encName = `table.Encode[${channel - 1}].ExtraFormat[${i - 1}]`;
|
||||
}
|
||||
|
||||
const videoCodec = findValue(encodeResponse.data, encName, 'Video.Compression')
|
||||
const videoCodec = findValue(encodeResponse.body, encName, 'Video.Compression')
|
||||
?.replace('.', '')?.toLowerCase()?.trim();
|
||||
let audioCodec = findValue(encodeResponse.data, encName, 'Audio.Compression')
|
||||
let audioCodec = findValue(encodeResponse.body, encName, 'Audio.Compression')
|
||||
?.replace('.', '')?.toLowerCase()?.trim();
|
||||
if (audioCodec?.includes('aac'))
|
||||
audioCodec = 'aac';
|
||||
@@ -409,18 +400,18 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
vso.audio.codec = audioCodec;
|
||||
vso.video.codec = videoCodec;
|
||||
|
||||
const width = findValue(encodeResponse.data, encName, 'Video.Width');
|
||||
const height = findValue(encodeResponse.data, encName, 'Video.Height');
|
||||
const width = findValue(encodeResponse.body, encName, 'Video.Width');
|
||||
const height = findValue(encodeResponse.body, encName, 'Video.Height');
|
||||
if (width && height) {
|
||||
vso.video.width = parseInt(width);
|
||||
vso.video.height = parseInt(height);
|
||||
}
|
||||
|
||||
const bitrateOptions = findValue(capResponse.data, capName, 'Video.BitRateOptions');
|
||||
const bitrateOptions = findValue(capResponse.body, capName, 'Video.BitRateOptions');
|
||||
if (!bitrateOptions)
|
||||
continue;
|
||||
|
||||
const encodeOptions = findValue(encodeResponse.data, encName, 'Video.BitRate');
|
||||
const encodeOptions = findValue(encodeResponse.body, encName, 'Video.BitRate');
|
||||
if (!encodeOptions)
|
||||
continue;
|
||||
|
||||
@@ -538,16 +529,15 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
// seems the dahua doorbells preferred 1024 chunks. should investigate adts
|
||||
// parsing and sending multipart chunks instead.
|
||||
const passthrough = new PassThrough();
|
||||
this.getClient().digestAuth.request({
|
||||
method: 'POST',
|
||||
this.getClient().request({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'Audio/AAC',
|
||||
'Content-Length': '9999999'
|
||||
},
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
data: passthrough,
|
||||
});
|
||||
responseType: 'readable',
|
||||
}, passthrough);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import https from 'https';
|
||||
|
||||
export const amcrestHttpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
import { AuthFetchCredentialState, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
|
||||
// appAutoStart=true
|
||||
// deviceType=IP4M-1041B
|
||||
@@ -11,17 +7,16 @@ export const amcrestHttpsAgent = new https.Agent({
|
||||
// serialNumber=12345
|
||||
// updateSerial=IPC-AW46WN-S2
|
||||
|
||||
import AxiosDigestAuth from "@koush/axios-digest-auth";
|
||||
|
||||
// updateSerialCloudUpgrade=IPC-AW46WN-.....
|
||||
export async function getDeviceInfo(digestAuth: AxiosDigestAuth, address: string) {
|
||||
const response = await digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'text',
|
||||
export async function getDeviceInfo(credential: AuthFetchCredentialState, address: string) {
|
||||
const response = await authHttpFetch({
|
||||
credential,
|
||||
url: `http://${address}/cgi-bin/magicBox.cgi?action=getSystemInfo`,
|
||||
rejectUnauthorized: false,
|
||||
responseType: 'text',
|
||||
});
|
||||
const lines = (response.data as string).split('\n');
|
||||
const lines = response.body.split('\n');
|
||||
const vals: {
|
||||
[key: string]: string,
|
||||
} = {};
|
||||
|
||||
4
plugins/core/.vscode/launch.json
vendored
4
plugins/core/.vscode/launch.json
vendored
@@ -10,14 +10,14 @@
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"**/plugin-remote-worker.*",
|
||||
"**/plugin-console.*",
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"sourceMaps": true,
|
||||
"localRoot": "${workspaceFolder}/out",
|
||||
"remoteRoot": "/plugin/",
|
||||
"type": "pwa-node"
|
||||
"type": "node"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
plugins/core/package-lock.json
generated
6
plugins/core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.150",
|
||||
"version": "0.2.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.150",
|
||||
"version": "0.2.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
@@ -88,7 +88,7 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.108",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.150",
|
||||
"version": "0.2.3",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export class Timer {
|
||||
|
||||
}
|
||||
@@ -9,14 +9,12 @@ import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
|
||||
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
|
||||
import { LauncherMixin } from './launcher-mixin';
|
||||
import { MediaCore } from './media-core';
|
||||
import { ScriptCore, ScriptCoreNativeId } from './script-core';
|
||||
import { UsersCore, UsersNativeId } from './user';
|
||||
import { newScript, ScriptCore, ScriptCoreNativeId } from './script-core';
|
||||
import { TerminalService, TerminalServiceNativeId } from './terminal-service';
|
||||
import { UsersCore, UsersNativeId } from './user';
|
||||
|
||||
const { systemManager, deviceManager, endpointManager } = sdk;
|
||||
|
||||
const indexHtml = fs.readFileSync('dist/index.html').toString();
|
||||
|
||||
export function getAddresses() {
|
||||
const addresses: string[] = [];
|
||||
for (const [iface, nif] of Object.entries(os.networkInterfaces())) {
|
||||
@@ -61,10 +59,14 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
},
|
||||
}
|
||||
});
|
||||
indexHtml: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
||||
this.indexHtml = fs.readFileSync('dist/index.html').toString();
|
||||
|
||||
(async () => {
|
||||
await deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
@@ -226,7 +228,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
const endpoint = await endpointManager.getPublicCloudEndpoint();
|
||||
const u = new URL(endpoint);
|
||||
|
||||
const rewritten = indexHtml
|
||||
const rewritten = this.indexHtml
|
||||
.replace('href="manifest.json"', `href="manifest.json${u.search}"`)
|
||||
.replace('href="img/icons/apple-touch-icon-152x152.png"', `href="img/icons/apple-touch-icon-152x152.png${u.search}"`)
|
||||
.replace('href="img/icons/safari-pinned-tab.svg"', `href="img/icons/safari-pinned-tab.svg${u.search}"`)
|
||||
@@ -269,5 +271,6 @@ export default ScryptedCore;
|
||||
export async function fork() {
|
||||
return {
|
||||
tsCompile,
|
||||
newScript,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
import { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
|
||||
import { Script } from "./script";
|
||||
import sdk from '@scrypted/sdk';
|
||||
import sdk, { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting } from '@scrypted/sdk';
|
||||
import { randomBytes } from "crypto";
|
||||
import fs from 'fs';
|
||||
import path from "path/posix";
|
||||
import { Worker } from "worker_threads";
|
||||
import { Script } from "./script";
|
||||
|
||||
const { deviceManager } = sdk;
|
||||
export const ScriptCoreNativeId = 'scriptcore';
|
||||
|
||||
interface ScriptWorker {
|
||||
script: Script;
|
||||
worker: Worker;
|
||||
}
|
||||
|
||||
export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator, Readme {
|
||||
scripts = new Map<string, Promise<Script>>();
|
||||
scripts = new Map<string, ScriptWorker>();
|
||||
|
||||
constructor() {
|
||||
super(ScriptCoreNativeId);
|
||||
|
||||
for (const nativeId of deviceManager.getNativeIds()) {
|
||||
if (nativeId?.startsWith('script:')) {
|
||||
const script = new Script(nativeId);
|
||||
this.scripts.set(nativeId, (async () => {
|
||||
if (script.providedInterfaces.length > 2) {
|
||||
await script.run();
|
||||
}
|
||||
else {
|
||||
this.reportScript(nativeId);
|
||||
}
|
||||
return script;
|
||||
})());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getCreateDeviceSettings(): Promise<Setting[]> {
|
||||
@@ -51,6 +41,10 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
|
||||
const nativeId = 'script:' + randomBytes(8).toString('hex');
|
||||
await this.reportScript(nativeId, name?.toString());
|
||||
const script = new Script(nativeId);
|
||||
this.scripts.set(nativeId, {
|
||||
script,
|
||||
worker: undefined,
|
||||
});
|
||||
if (template) {
|
||||
try {
|
||||
await script.saveScript({
|
||||
@@ -65,7 +59,6 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
this.scripts.set(nativeId, Promise.resolve(script));
|
||||
return nativeId;
|
||||
}
|
||||
|
||||
@@ -84,10 +77,75 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
|
||||
return await deviceManager.onDeviceDiscovered(device);
|
||||
}
|
||||
|
||||
getDevice(nativeId: string) {
|
||||
return this.scripts.get(nativeId);
|
||||
async getDevice(nativeId: string) {
|
||||
const e = this.scripts.get(nativeId);
|
||||
if (e) {
|
||||
if (e.script)
|
||||
return e.script;
|
||||
e.worker?.terminate();
|
||||
this.scripts.delete(nativeId);
|
||||
}
|
||||
|
||||
let script = new Script(nativeId);
|
||||
let worker: Worker;
|
||||
|
||||
const triggerDeviceDiscover = async (name: string, type: ScryptedDeviceType, interfaces: string[]) => {
|
||||
const e = this.scripts.get(nativeId);
|
||||
if (e?.script == script)
|
||||
e.script = undefined;
|
||||
|
||||
const device: Device = {
|
||||
providerNativeId: this.nativeId,
|
||||
name,
|
||||
nativeId,
|
||||
type,
|
||||
interfaces,
|
||||
refresh: true,
|
||||
};
|
||||
return await deviceManager.onDeviceDiscovered(device);
|
||||
};
|
||||
|
||||
if (script.providedInterfaces.length > 2) {
|
||||
const fork = sdk.fork<{
|
||||
newScript: typeof newScript,
|
||||
}>();
|
||||
worker = fork.worker;
|
||||
try {
|
||||
script = await (await fork.result).newScript(nativeId, triggerDeviceDiscover);
|
||||
}
|
||||
catch (e) {
|
||||
worker.terminate();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
worker?.on('exit', () => {
|
||||
if (this.scripts.get(nativeId)?.worker === worker) {
|
||||
this.scripts.delete(nativeId);
|
||||
// notify the system that the device needs to be refreshed.
|
||||
if (deviceManager.getNativeIds().includes(nativeId)) {
|
||||
const script = new Script(nativeId);
|
||||
triggerDeviceDiscover(script.providedName, script.providedType, script.providedInterfaces);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.scripts.set(nativeId, {
|
||||
script,
|
||||
worker,
|
||||
});
|
||||
return script;
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
const worker = this.scripts.get(nativeId)?.worker;
|
||||
this.scripts.delete(nativeId);
|
||||
worker?.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
export async function newScript(nativeId: ScryptedNativeId, triggerDeviceDiscover: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
|
||||
const script = new Script(nativeId, triggerDeviceDiscover);
|
||||
await script.run();
|
||||
return script;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@ import { createScriptDevice, ScriptDeviceImpl } from "@scrypted/common/src/eval/
|
||||
import { ScriptCoreNativeId } from "./script-core";
|
||||
import { PluginAPIProxy } from "../../../server/src/plugin/plugin-api";
|
||||
|
||||
const { log, deviceManager, systemManager } = sdk;
|
||||
const { deviceManager } = sdk;
|
||||
|
||||
export class Script extends ScryptedDeviceBase implements Scriptable, Program, ScriptDeviceImpl {
|
||||
apiProxy: PluginAPIProxy;
|
||||
|
||||
constructor(nativeId: string) {
|
||||
constructor(nativeId: string, public triggerDeviceDiscover?: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
|
||||
super(nativeId);
|
||||
}
|
||||
|
||||
@@ -18,6 +16,8 @@ export class Script extends ScryptedDeviceBase implements Scriptable, Program, S
|
||||
this.storage.setItem('data', JSON.stringify({
|
||||
'script.ts': source.script,
|
||||
}));
|
||||
|
||||
this.triggerDeviceDiscover?.(this.providedName, this.providedType, this.providedInterfaces);
|
||||
}
|
||||
|
||||
async loadScripts(): Promise<{ [filename: string]: ScriptSource; }> {
|
||||
@@ -70,46 +70,38 @@ export class Script extends ScryptedDeviceBase implements Scriptable, Program, S
|
||||
}
|
||||
|
||||
prepareScript() {
|
||||
this.apiProxy?.removeListeners();
|
||||
|
||||
Object.assign(this, createScriptDevice([
|
||||
ScryptedInterface.Scriptable,
|
||||
ScryptedInterface.Program,
|
||||
]));
|
||||
}
|
||||
|
||||
async run(variables?: { [name: string]: any; }): Promise<any> {
|
||||
async runInternal(script: string, variables?: { [name: string]: any; }): Promise<any> {
|
||||
this.prepareScript();
|
||||
|
||||
try {
|
||||
const data = JSON.parse(this.storage.getItem('data'));
|
||||
|
||||
const { value, defaultExport, apiProxy } = await scryptedEval(this, data['script.ts'], Object.assign({
|
||||
const { value, defaultExport } = await scryptedEval(this, script, Object.assign({
|
||||
device: this,
|
||||
}, variables));
|
||||
|
||||
this.apiProxy = apiProxy;
|
||||
|
||||
await this.postRunScript(defaultExport);
|
||||
return value;
|
||||
}
|
||||
catch (e) {
|
||||
this.console.error('error loading script', e);
|
||||
this.console.error('error evaluating script', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async run(variables?: { [name: string]: any; }): Promise<any> {
|
||||
const data = JSON.parse(this.storage.getItem('data'));
|
||||
return this.runInternal(data['script.ts'], variables)
|
||||
}
|
||||
|
||||
async eval(source: ScriptSource, variables?: { [name: string]: any }) {
|
||||
this.prepareScript();
|
||||
|
||||
const { value, defaultExport, apiProxy } = await scryptedEval(this, source.script, Object.assign({
|
||||
device: this,
|
||||
}, variables));
|
||||
|
||||
this.apiProxy = apiProxy;
|
||||
|
||||
await this.postRunScript(defaultExport);
|
||||
return value;
|
||||
return this.runInternal(source.script, variables);
|
||||
}
|
||||
|
||||
// will be done at runtime
|
||||
|
||||
@@ -173,7 +173,7 @@ export default {
|
||||
const version = await info.getVersion();
|
||||
const scryptedEnv = await info.getScryptedEnv();
|
||||
this.currentVersion = version;
|
||||
const { updateAvailable } = await checkServerUpdate(version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
|
||||
const { updateAvailable } = await checkServerUpdate(this.$scrypted.mediaManager, version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
|
||||
this.updateAvailable = updateAvailable;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
// old scrypted servers dont support this call, or it may be unimplemented
|
||||
// in which case fall back and determine what the install type is.
|
||||
const scryptedEnv = await info.getScryptedEnv();
|
||||
const { updateAvailable } = await checkServerUpdate(version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
|
||||
const { updateAvailable } = await checkServerUpdate(this.$scrypted.mediaManager, version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
|
||||
this.updateAvailable = updateAvailable;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeviceCreator, Scriptable, ScryptedInterface, ScryptedStatic, SystemManager } from '@scrypted/types';
|
||||
import { DeviceCreator, MediaObject, Scriptable, ScryptedInterface, ScryptedStatic, SystemManager, MediaManager } from '@scrypted/types';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import semver from 'semver';
|
||||
import { getAllDevices } from '../../common/mixin';
|
||||
@@ -66,7 +66,7 @@ export async function checkUpdate(npmPackage: string, npmPackageVersion: string)
|
||||
};
|
||||
}
|
||||
|
||||
export async function checkServerUpdate(version: string, installEnvironment: string): Promise<PluginUpdateCheck> {
|
||||
export async function checkServerUpdate(mediaManager: MediaManager, version: string, installEnvironment: string): Promise<PluginUpdateCheck> {
|
||||
const { updateAvailable, updatePublished, versions } = await checkUpdate(
|
||||
"@scrypted/server",
|
||||
version
|
||||
@@ -78,8 +78,9 @@ export async function checkServerUpdate(version: string, installEnvironment: str
|
||||
// check if there is a new docker image available, using 'latest' tag
|
||||
// this is done so newer server versions in npm are not immediately
|
||||
// displayed until a docker image has been published
|
||||
let response: AxiosResponse<any> = await axios.get("https://corsproxy.io?https://hub.docker.com/v2/namespaces/koush/repositories/scrypted/tags/latest");
|
||||
const { data } = response;
|
||||
// due to CORS restrictions, we query this through scrypted server
|
||||
const mo: MediaObject = await mediaManager.createMediaObjectFromUrl('https://hub.docker.com/v2/namespaces/koush/repositories/scrypted/tags/latest');
|
||||
const data: any = await mediaManager.convertMediaObjectToJSON(mo, 'application/json');
|
||||
const imagePublished = new Date(data.last_updated);
|
||||
console.log(`Latest docker image published ${imagePublished}`);
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ export default {
|
||||
|
||||
const fs = 20;
|
||||
|
||||
const box = `<rect x="${x}" y="${y}" width="${w}" height="${h}" stroke="${s}" stroke-width="2" fill="none" />
|
||||
const box = `<rect x="${x}" y="${y}" width="${w}" height="${h}" stroke="${s}" stroke-width="1px" fill="none" />
|
||||
<text x="${x}" y="${y}" font-size="${fs}" dx="0.05em" dy="0.05em" fill="black">${t}</text>
|
||||
<text x="${x}" y="${y}" font-size="${fs}" fill="white">${t}</text>
|
||||
`;
|
||||
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
for (const detection of this.lastDetection.detections || []) {
|
||||
if (!detection.boundingBox) continue;
|
||||
const svgScale = this.svgWidth / 1080;
|
||||
const sw = 6 * svgScale;
|
||||
const sw = 1;
|
||||
const s = "red";
|
||||
const x = detection.boundingBox[0];
|
||||
const y = detection.boundingBox[1];
|
||||
|
||||
162
plugins/ffmpeg-camera/package-lock.json
generated
162
plugins/ffmpeg-camera/package-lock.json
generated
@@ -9,40 +9,37 @@
|
||||
"version": "0.0.22",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"url-parse": "^1.4.7"
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6"
|
||||
"@types/node": "^20.10.8"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.103",
|
||||
"dev": true,
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -75,15 +72,6 @@
|
||||
"../sdk": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
|
||||
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
|
||||
"dependencies": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
@@ -93,82 +81,32 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
|
||||
"integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==",
|
||||
"version": "20.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
|
||||
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
|
||||
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
|
||||
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
|
||||
"requires": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
}
|
||||
},
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^16.9.0",
|
||||
"@types/node": "^20.10.8",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
@@ -178,7 +116,7 @@
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -196,47 +134,19 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
|
||||
"integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==",
|
||||
"version": "20.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
|
||||
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
},
|
||||
"querystringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
|
||||
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
|
||||
},
|
||||
"requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,10 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6"
|
||||
"@types/node": "^20.10.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"url-parse": "^1.4.7"
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,21 @@
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import sdk, { Camera, DeviceCreator, DeviceCreatorSettings, DeviceProvider, MediaObject, PictureOptions, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, Settings, SettingValue, VideoCamera } from "@scrypted/sdk";
|
||||
import { randomBytes } from "crypto";
|
||||
import https from 'https';
|
||||
|
||||
const { deviceManager, mediaManager } = sdk;
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
const { deviceManager } = sdk;
|
||||
|
||||
export interface UrlMediaStreamOptions extends ResponseMediaStreamOptions {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export abstract class CameraBase<T extends ResponseMediaStreamOptions> extends ScryptedDeviceBase implements Camera, VideoCamera, Settings {
|
||||
snapshotAuth: AxiosDigestAuth;
|
||||
|
||||
constructor(nativeId: string, public provider: CameraProviderBase<T>) {
|
||||
super(nativeId);
|
||||
}
|
||||
|
||||
protected async takePictureUrl(snapshotUrl: string) {
|
||||
if (!this.snapshotAuth) {
|
||||
this.snapshotAuth = new AxiosDigestAuth({
|
||||
username: this.getUsername(),
|
||||
password: this.getPassword(),
|
||||
});
|
||||
}
|
||||
const response = await this.snapshotAuth.request({
|
||||
httpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
url: snapshotUrl,
|
||||
});
|
||||
|
||||
return mediaManager.createMediaObject(Buffer.from(response.data), response.headers['Content-Type'] || 'image/jpeg');
|
||||
takePicture(option?: PictureOptions): Promise<MediaObject> {
|
||||
throw new Error("The RTSP Camera does not provide snapshots. Install the Snapshot Plugin if snapshots are available via an URL.");
|
||||
}
|
||||
|
||||
abstract takePicture(option?: PictureOptions): Promise<MediaObject>;
|
||||
|
||||
async getPictureOptions(): Promise<PictureOptions[]> {
|
||||
return;
|
||||
}
|
||||
@@ -140,8 +117,6 @@ export abstract class CameraBase<T extends ResponseMediaStreamOptions> extends S
|
||||
this.storage.setItem(key, value.toString());
|
||||
}
|
||||
|
||||
this.snapshotAuth = undefined;
|
||||
|
||||
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import sdk, { FFmpegInput, MediaObject, PictureOptions, Setting, SettingValue } from "@scrypted/sdk";
|
||||
import sdk, { FFmpegInput, MediaObject, Setting, SettingValue } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import { CameraBase, CameraProviderBase, UrlMediaStreamOptions } from "./common";
|
||||
|
||||
@@ -15,10 +15,6 @@ function parseDoubleQuotedArguments(input: string) {
|
||||
}
|
||||
|
||||
class FFmpegCamera extends CameraBase<UrlMediaStreamOptions> {
|
||||
takePictureThrottled(option?: PictureOptions): Promise<MediaObject> {
|
||||
throw new Error("The RTSP Camera does not provide snapshots. Install the Snapshot Plugin if snapshots are available via an URL.");
|
||||
}
|
||||
|
||||
storageSettings = new StorageSettings(this, {
|
||||
ffmpegInputs: {
|
||||
title: 'FFmpeg Input Stream Arguments',
|
||||
|
||||
6143
plugins/gstreamer-camera/package-lock.json
generated
6143
plugins/gstreamer-camera/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -32,12 +32,10 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6"
|
||||
"@types/node": "^20.10.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"url-parse": "^1.4.7"
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, ScryptedDeviceType, VideoCamera, MediaObject, MediaStreamOptions, ScryptedInterface, FFmpegInput, Camera, PictureOptions, SettingValue, DeviceCreator, DeviceCreatorSettings, ResponseMediaStreamOptions } from "@scrypted/sdk";
|
||||
import { recommendRebroadcast } from "./recommend";
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import https from 'https';
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
const { log, deviceManager, mediaManager } = sdk;
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
export interface UrlMediaStreamOptions extends MediaStreamOptions {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export abstract class CameraBase<T extends ResponseMediaStreamOptions> extends ScryptedDeviceBase implements Camera, VideoCamera, Settings {
|
||||
snapshotAuth: AxiosDigestAuth;
|
||||
pendingPicture: Promise<MediaObject>;
|
||||
|
||||
constructor(nativeId: string, public provider: CameraProviderBase<T>) {
|
||||
super(nativeId);
|
||||
}
|
||||
|
||||
getSnapshotUrl() {
|
||||
return this.storage.getItem('snapshotUrl');
|
||||
}
|
||||
|
||||
async takePicture(option?: PictureOptions): Promise<MediaObject> {
|
||||
if (!this.pendingPicture) {
|
||||
this.pendingPicture = this.takePictureThrottled(option);
|
||||
this.pendingPicture.finally(() => this.pendingPicture = undefined);
|
||||
}
|
||||
|
||||
return this.pendingPicture;
|
||||
}
|
||||
|
||||
async takePictureThrottled(option?: PictureOptions): Promise<MediaObject> {
|
||||
const snapshotUrl = this.getSnapshotUrl();
|
||||
if (!snapshotUrl) {
|
||||
throw new Error('Camera has no snapshot URL');
|
||||
}
|
||||
|
||||
if (!this.snapshotAuth) {
|
||||
this.snapshotAuth = new AxiosDigestAuth({
|
||||
username: this.getUsername(),
|
||||
password: this.getPassword(),
|
||||
});
|
||||
}
|
||||
|
||||
const response = await this.snapshotAuth.request({
|
||||
httpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
url: snapshotUrl,
|
||||
});
|
||||
|
||||
return mediaManager.createMediaObject(Buffer.from(response.data), response.headers['Content-Type'] || 'image/jpeg');
|
||||
}
|
||||
|
||||
async getPictureOptions(): Promise<PictureOptions[]> {
|
||||
return;
|
||||
}
|
||||
|
||||
getDefaultOrderedVideoStreamOptions(vsos: T[]) {
|
||||
if (!vsos || !vsos.length)
|
||||
return vsos;
|
||||
const defaultStream = this.getDefaultStream(vsos);
|
||||
if (!defaultStream)
|
||||
return vsos;
|
||||
vsos = vsos.filter(vso => vso.id !== defaultStream?.id);
|
||||
vsos.unshift(defaultStream);
|
||||
return vsos;
|
||||
}
|
||||
|
||||
async getVideoStreamOptions(): Promise<T[]> {
|
||||
let vsos = this.getRawVideoStreamOptions();
|
||||
return this.getDefaultOrderedVideoStreamOptions(vsos);
|
||||
}
|
||||
|
||||
abstract getRawVideoStreamOptions(): T[];
|
||||
|
||||
isAudioDisabled() {
|
||||
return this.storage.getItem('noAudio') === 'true';
|
||||
}
|
||||
|
||||
async getVideoStream(options?: T): Promise<MediaObject> {
|
||||
const vsos = await this.getVideoStreamOptions();
|
||||
const vso = vsos?.find(s => s.id === options?.id) || this.getDefaultStream(vsos);
|
||||
return this.createVideoStream(vso);
|
||||
}
|
||||
|
||||
abstract createVideoStream(options?: T): Promise<MediaObject>;
|
||||
|
||||
async getSnapshotUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'snapshotUrl',
|
||||
title: 'Snapshot URL',
|
||||
placeholder: 'http://192.168.1.100[:80]/snapshot.jpg',
|
||||
value: this.getSnapshotUrl(),
|
||||
description: 'Optional: The snapshot URL that will returns the current JPEG image.'
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async getUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
...await this.getSnapshotUrlSettings(),
|
||||
];
|
||||
}
|
||||
|
||||
getUsername() {
|
||||
return this.storage.getItem('username');
|
||||
}
|
||||
|
||||
getPassword() {
|
||||
return this.storage.getItem('password');
|
||||
}
|
||||
|
||||
async getOtherSettings(): Promise<Setting[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
getDefaultStream(vsos: T[]) {
|
||||
let defaultStreamIndex = vsos?.findIndex(vso => vso.id === this.storage.getItem('defaultStream'));
|
||||
if (defaultStreamIndex === -1)
|
||||
defaultStreamIndex = 0;
|
||||
|
||||
defaultStreamIndex = defaultStreamIndex || 0;
|
||||
return vsos?.[defaultStreamIndex];
|
||||
}
|
||||
|
||||
async getStreamSettings(): Promise<Setting[]> {
|
||||
try {
|
||||
const vsos = await this.getVideoStreamOptions();
|
||||
if (!vsos?.length || vsos?.length === 1)
|
||||
return [];
|
||||
|
||||
|
||||
const defaultStream = this.getDefaultStream(vsos);
|
||||
return [
|
||||
{
|
||||
title: 'Default Stream',
|
||||
key: 'defaultStream',
|
||||
value: defaultStream?.name,
|
||||
choices: vsos.map(vso => vso.name),
|
||||
description: 'The default stream to use when not specified',
|
||||
}
|
||||
];
|
||||
}
|
||||
catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getUsernameDescription(): string {
|
||||
return 'Optional: Username for snapshot http requests.';
|
||||
}
|
||||
|
||||
getPasswordDescription(): string {
|
||||
return 'Optional: Password for snapshot http requests.';
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'username',
|
||||
title: 'Username',
|
||||
value: this.getUsername(),
|
||||
description: this.getUsernameDescription(),
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
title: 'Password',
|
||||
value: this.getPassword(),
|
||||
type: 'password',
|
||||
description: this.getPasswordDescription(),
|
||||
},
|
||||
...await this.getUrlSettings(),
|
||||
...await this.getStreamSettings(),
|
||||
...await this.getOtherSettings(),
|
||||
{
|
||||
key: 'noAudio',
|
||||
title: 'No Audio',
|
||||
description: 'Enable this setting if the camera does not have audio or to mute audio.',
|
||||
type: 'boolean',
|
||||
value: (this.isAudioDisabled()).toString(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async putSettingBase(key: string, value: SettingValue) {
|
||||
if (key === 'defaultStream') {
|
||||
const vsos = await this.getVideoStreamOptions();
|
||||
const stream = vsos.find(vso => vso.name === value);
|
||||
this.storage.setItem('defaultStream', stream?.id || '');
|
||||
}
|
||||
else {
|
||||
this.storage.setItem(key, value.toString());
|
||||
}
|
||||
|
||||
this.snapshotAuth = undefined;
|
||||
|
||||
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: SettingValue) {
|
||||
this.putSettingBase(key, value);
|
||||
|
||||
if (key === 'snapshotUrl') {
|
||||
let interfaces = this.providedInterfaces;
|
||||
if (!value)
|
||||
interfaces = interfaces.filter(iface => iface !== ScryptedInterface.Camera)
|
||||
else
|
||||
interfaces.push(ScryptedInterface.Camera);
|
||||
|
||||
this.provider.updateDevice(this.nativeId, this.providedName, interfaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class CameraProviderBase<T extends ResponseMediaStreamOptions> extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
|
||||
devices = new Map<string, any>();
|
||||
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
|
||||
for (const camId of deviceManager.getNativeIds()) {
|
||||
if (camId)
|
||||
this.getDevice(camId);
|
||||
}
|
||||
|
||||
recommendRebroadcast();
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
}
|
||||
|
||||
async createDevice(settings: DeviceCreatorSettings): Promise<string> {
|
||||
const nativeId = randomBytes(4).toString('hex');
|
||||
const name = settings.newCamera.toString();
|
||||
await this.updateDevice(nativeId, name, this.getInterfaces());
|
||||
return nativeId;
|
||||
}
|
||||
|
||||
async getCreateDeviceSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'newCamera',
|
||||
title: 'Add Camera',
|
||||
placeholder: 'Camera name, e.g.: Back Yard Camera, Baby Camera, etc',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
getAdditionalInterfaces(): string[] {
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
getInterfaces() {
|
||||
return [ScryptedInterface.VideoCamera,
|
||||
ScryptedInterface.Settings, ...this.getAdditionalInterfaces()];
|
||||
}
|
||||
|
||||
updateDevice(nativeId: string, name: string, interfaces: string[], type?: ScryptedDeviceType) {
|
||||
return deviceManager.onDeviceDiscovered({
|
||||
nativeId,
|
||||
name,
|
||||
interfaces,
|
||||
type: type || ScryptedDeviceType.Camera,
|
||||
});
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: string | number) {
|
||||
// generate a random id
|
||||
const nativeId = randomBytes(4).toString('hex');
|
||||
const name = value.toString();
|
||||
|
||||
this.updateDevice(nativeId, name, this.getInterfaces());
|
||||
}
|
||||
|
||||
abstract createCamera(nativeId: string): CameraBase<T>;
|
||||
|
||||
getDevice(nativeId: string) {
|
||||
let ret = this.devices.get(nativeId);
|
||||
if (!ret) {
|
||||
ret = this.createCamera(nativeId);
|
||||
if (ret)
|
||||
this.devices.set(nativeId, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import sdk, { FFmpegInput, MediaObject, MediaStreamOptions, ResponseMediaStreamOptions, Setting, SettingValue } from "@scrypted/sdk";
|
||||
import child_process, { ChildProcess } from "child_process";
|
||||
import { CameraProviderBase, CameraBase, UrlMediaStreamOptions } from "./common";
|
||||
import { CameraProviderBase, CameraBase, UrlMediaStreamOptions } from "../../ffmpeg-camera/src/common";
|
||||
// import {} from "../../../common/src/stream-parser"
|
||||
// import {} from "../../../common/src/ffmpeg-rebroadcast"
|
||||
import net from 'net';
|
||||
@@ -81,7 +81,6 @@ class GStreamerCamera extends CameraBase<ResponseMediaStreamOptions> {
|
||||
|
||||
async getUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
...await this.getSnapshotUrlSettings(),
|
||||
...await this.getGStreamerInputSettings(),
|
||||
];
|
||||
}
|
||||
|
||||
232
plugins/hikvision/package-lock.json
generated
232
plugins/hikvision/package-lock.json
generated
@@ -1,19 +1,18 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"axios": "^1.4.0",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"xml2js": "^0.6.0"
|
||||
},
|
||||
@@ -30,15 +29,16 @@
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -76,23 +76,6 @@
|
||||
"../sdk": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.6.tgz",
|
||||
"integrity": "sha512-e/XKs7/BYpPQkces0Cm4dUmhT9hR0rjvnNZAVRyRnNWdQ8cyCMFWS9HIrMWOdzAocKDNBXi1vKjJ8CywrW5xgQ==",
|
||||
"dependencies": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth/node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
@@ -114,75 +97,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"node_modules/http-auth-utils": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-auth-utils/-/http-auth-utils-3.0.2.tgz",
|
||||
"integrity": "sha512-cQ8957aiUX0lgV1620uIGKGJc0sEuD/QK4ueZ0hb60MGbO0f6ahcuIgPjamAD98D/AUGizKVm+dNvUVHs0f4Ow==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
"yerror": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
"node": ">=12.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
@@ -190,30 +113,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
@@ -238,37 +137,27 @@
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yerror": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.2.1.tgz",
|
||||
"integrity": "sha512-WPZgybhCBzsMSSqGYBnj20NZo4FiFKQG0+i/21cYGVd4B7eYtvYDOpjk/0e8UM1eVHJ+4Ja6bZ7TAjHH6mk+ew==",
|
||||
"engines": {
|
||||
"node": ">=12.19.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.6.tgz",
|
||||
"integrity": "sha512-e/XKs7/BYpPQkces0Cm4dUmhT9hR0rjvnNZAVRyRnNWdQ8cyCMFWS9HIrMWOdzAocKDNBXi1vKjJ8CywrW5xgQ==",
|
||||
"requires": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^16.9.0",
|
||||
"@types/node": "^20.10.8",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
@@ -308,52 +197,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"http-auth-utils": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-auth-utils/-/http-auth-utils-3.0.2.tgz",
|
||||
"integrity": "sha512-cQ8957aiUX0lgV1620uIGKGJc0sEuD/QK4ueZ0hb60MGbO0f6ahcuIgPjamAD98D/AUGizKVm+dNvUVHs0f4Ow==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
"yerror": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
@@ -361,24 +210,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
@@ -397,6 +228,11 @@
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||
},
|
||||
"yerror": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.2.1.tgz",
|
||||
"integrity": "sha512-WPZgybhCBzsMSSqGYBnj20NZo4FiFKQG0+i/21cYGVd4B7eYtvYDOpjk/0e8UM1eVHJ+4Ja6bZ7TAjHH6mk+ew=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"description": "Hikvision Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -35,11 +35,10 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"axios": "^1.4.0",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"xml2js": "^0.6.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { getDeviceInfo, hikvisionHttpsAgent } from './probe';
|
||||
import { AuthFetchCredentialState, AuthFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { IncomingMessage, RequestOptions } from 'http';
|
||||
import { Readable } from 'stream';
|
||||
import { HttpFetchOptions, HttpFetchResponseType } from '../../../server/src/http-fetch-helpers';
|
||||
import { getDeviceInfo } from './probe';
|
||||
|
||||
export function getChannel(channel: string) {
|
||||
return channel || '101';
|
||||
@@ -28,47 +30,59 @@ export interface HikvisionCameraStreamSetup {
|
||||
}
|
||||
|
||||
export class HikvisionCameraAPI {
|
||||
digestAuth: AxiosDigestAuth;
|
||||
credential: AuthFetchCredentialState;
|
||||
deviceModel: Promise<string>;
|
||||
listenerPromise: Promise<IncomingMessage>;
|
||||
|
||||
constructor(public ip: string, username: string, password: string, public console: Console) {
|
||||
this.digestAuth = new AxiosDigestAuth({
|
||||
this.credential = {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async request<T extends HttpFetchResponseType>(urlOrOptions: string | URL | HttpFetchOptions<T>, body?: Readable) {
|
||||
const options: AuthFetchOptions<T> = {
|
||||
...typeof urlOrOptions !== 'string' && !(urlOrOptions instanceof URL) ? urlOrOptions : {
|
||||
url: urlOrOptions,
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
credential: this.credential,
|
||||
body,
|
||||
};
|
||||
|
||||
const response = await authHttpFetch(options);
|
||||
return response;
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/ISAPI/System/reboot`,
|
||||
method: "PUT",
|
||||
responseType: 'text',
|
||||
url: `http://${this.ip}/ISAPI/System/reboot`,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async getDeviceInfo() {
|
||||
return getDeviceInfo(this.digestAuth, this.ip);
|
||||
return getDeviceInfo(this.credential, this.ip);
|
||||
}
|
||||
|
||||
async checkTwoWayAudio() {
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'text',
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/ISAPI/System/TwoWayAudio/channels`,
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
return (response.data as string).includes('Speaker');
|
||||
return response.body.includes('Speaker');
|
||||
}
|
||||
|
||||
async checkDeviceModel(): Promise<string> {
|
||||
if (!this.deviceModel) {
|
||||
this.deviceModel = this.getDeviceInfo().then(d => d.deviceModel).catch(e => {
|
||||
this.console.error('error checking NVR model', e);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return await this.deviceModel;
|
||||
@@ -91,17 +105,15 @@ export class HikvisionCameraAPI {
|
||||
}
|
||||
}
|
||||
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'text',
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/ISAPI/Streaming/channels/${getChannel(channel)}/capabilities`,
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
// this is bad:
|
||||
// <videoCodecType opt="H.264,H.265">H.265</videoCodecType>
|
||||
const vcodec = response.data.match(/>(.*?)<\/videoCodecType>/);
|
||||
const acodec = response.data.match(/>(.*?)<\/audioCompressionType>/);
|
||||
const vcodec = response.body.match(/>(.*?)<\/videoCodecType>/);
|
||||
const acodec = response.body.match(/>(.*?)<\/audioCompressionType>/);
|
||||
|
||||
return {
|
||||
videoCodecType: vcodec?.[1],
|
||||
@@ -112,15 +124,12 @@ export class HikvisionCameraAPI {
|
||||
async jpegSnapshot(channel: string): Promise<Buffer> {
|
||||
const url = `http://${this.ip}/ISAPI/Streaming/channels/${getChannel(channel)}/picture?snapShotImageType=JPEG`
|
||||
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
const response = await this.request({
|
||||
url: url,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
return Buffer.from(response.data);
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async listenEvents() {
|
||||
@@ -128,13 +137,11 @@ export class HikvisionCameraAPI {
|
||||
if (!this.listenerPromise) {
|
||||
const url = `http://${this.ip}/ISAPI/Event/notification/alertStream`;
|
||||
|
||||
this.listenerPromise = this.digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: "GET",
|
||||
this.listenerPromise = this.request({
|
||||
url,
|
||||
responseType: 'stream',
|
||||
responseType: 'readable',
|
||||
}).then(response => {
|
||||
const stream = response.data as IncomingMessage;
|
||||
const stream = response.body;
|
||||
stream.socket.setKeepAlive(true);
|
||||
|
||||
stream.on('data', (buffer: Buffer) => {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
|
||||
import { RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
|
||||
import { startRtpForwarderProcess } from '../../webrtc/src/rtp-forwarders';
|
||||
import { HikvisionCameraAPI, HikvisionCameraEvent } from "./hikvision-camera-api";
|
||||
import { hikvisionHttpsAgent } from './probe';
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
@@ -207,12 +206,11 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
try {
|
||||
let xml: string;
|
||||
try {
|
||||
const response = await client.digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
const response = await client.request({
|
||||
url: `http://${this.getHttpAddress()}/ISAPI/Streaming/channels`,
|
||||
responseType: 'text',
|
||||
});
|
||||
xml = response.data;
|
||||
xml = response.body;
|
||||
this.storage.setItem('channels', xml);
|
||||
}
|
||||
catch (e) {
|
||||
@@ -382,12 +380,12 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
|
||||
try {
|
||||
const parameters = `http://${this.getHttpAddress()}/ISAPI/System/TwoWayAudio/channels`;
|
||||
const { data: parametersData } = await this.getClient().digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
const { body } = await this.getClient().request({
|
||||
url: parameters,
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
const parsedXml = await xml2js.parseStringPromise(parametersData);
|
||||
const parsedXml = await xml2js.parseStringPromise(body);
|
||||
for (const twoWayChannel of parsedXml.TwoWayAudioChannelList.TwoWayAudioChannel) {
|
||||
const [id] = twoWayChannel.id;
|
||||
if (id !== channel)
|
||||
@@ -423,27 +421,25 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
|
||||
const passthrough = new PassThrough();
|
||||
const open = `http://${this.getHttpAddress()}/ISAPI/System/TwoWayAudio/channels/${channel}/open`;
|
||||
const { data } = await this.getClient().digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: 'PUT',
|
||||
const { body } = await this.getClient().request({
|
||||
url: open,
|
||||
responseType: 'text',
|
||||
method: 'PUT',
|
||||
});
|
||||
this.console.log('two way audio opened', data);
|
||||
this.console.log('two way audio opened', body);
|
||||
|
||||
const url = `http://${this.getHttpAddress()}/ISAPI/System/TwoWayAudio/channels/${channel}/audioData`;
|
||||
this.console.log('posting audio data to', url);
|
||||
|
||||
const put = this.getClient().digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: 'PUT',
|
||||
const put = this.getClient().request({
|
||||
url,
|
||||
responseType: 'readable',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
// 'Connection': 'close',
|
||||
'Content-Length': '0'
|
||||
},
|
||||
data: passthrough,
|
||||
});
|
||||
}, passthrough);
|
||||
|
||||
let available = Buffer.alloc(0);
|
||||
this.activeIntercom?.kill();
|
||||
@@ -484,11 +480,10 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
}
|
||||
|
||||
const client = this.getClient();
|
||||
await client.digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: 'PUT',
|
||||
await client.request({
|
||||
url: `http://${this.getHttpAddress()}/ISAPI/System/TwoWayAudio/channels/${this.getRtspChannel() || '1'}/close`,
|
||||
})
|
||||
method: 'PUT',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,31 @@
|
||||
import https from 'https';
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import { AuthFetchCredentialState, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { checkStatus } from '../../../server/src/http-fetch-helpers';
|
||||
|
||||
export const hikvisionHttpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
export async function getDeviceInfo(digestAuth: AxiosDigestAuth, address: string) {
|
||||
try {
|
||||
const response = await digestAuth.request({
|
||||
httpsAgent: hikvisionHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'text',
|
||||
url: `http://${address}/ISAPI/System/deviceInfo`,
|
||||
});
|
||||
const deviceModel = response.data.match(/>(.*?)<\/model>/)?.[1];
|
||||
const deviceName = response.data.match(/>(.*?)<\/deviceName>/)?.[1];
|
||||
const serialNumber = response.data.match(/>(.*?)<\/serialNumber>/)?.[1];
|
||||
const macAddress = response.data.match(/>(.*?)<\/macAddress>/)?.[1];
|
||||
const firmwareVersion = response.data.match(/>(.*?)<\/firmwareVersion>/)?.[1];
|
||||
return {
|
||||
deviceModel,
|
||||
deviceName,
|
||||
serialNumber,
|
||||
macAddress,
|
||||
firmwareVersion,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
if (e?.response?.data?.includes('notActivated'))
|
||||
throw new Error(`Camera must be first be activated at http://${address}.`)
|
||||
throw e;
|
||||
}
|
||||
export async function getDeviceInfo(credential: AuthFetchCredentialState, address: string) {
|
||||
const response = await authHttpFetch({
|
||||
credential,
|
||||
url: `http://${address}/ISAPI/System/deviceInfo`,
|
||||
ignoreStatusCode: true,
|
||||
responseType: 'text',
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
if (response.body.includes('notActivated'))
|
||||
throw new Error(`Camera must be first be activated at http://${address}.`);
|
||||
|
||||
checkStatus(response.statusCode);
|
||||
|
||||
const deviceModel = response.body.match(/>(.*?)<\/model>/)?.[1];
|
||||
const deviceName = response.body.match(/>(.*?)<\/deviceName>/)?.[1];
|
||||
const serialNumber = response.body.match(/>(.*?)<\/serialNumber>/)?.[1];
|
||||
const macAddress = response.body.match(/>(.*?)<\/macAddress>/)?.[1];
|
||||
const firmwareVersion = response.body.match(/>(.*?)<\/firmwareVersion>/)?.[1];
|
||||
return {
|
||||
deviceModel,
|
||||
deviceName,
|
||||
serialNumber,
|
||||
macAddress,
|
||||
firmwareVersion,
|
||||
};
|
||||
}
|
||||
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.17",
|
||||
"version": "0.1.19",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.17",
|
||||
"version": "0.1.19",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.17",
|
||||
"version": "0.1.19",
|
||||
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -104,12 +104,13 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
analyzeStop: number;
|
||||
detectorSignal = new Deferred<void>().resolve();
|
||||
released = false;
|
||||
// settings: Setting[];
|
||||
|
||||
get detectorRunning() {
|
||||
return !this.detectorSignal.finished;
|
||||
}
|
||||
|
||||
constructor(public plugin: ObjectDetectionPlugin, mixinDevice: VideoCamera & Camera & MotionSensor & ObjectDetector & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string, public objectDetection: ObjectDetection & ScryptedDevice, public model: ObjectDetectionModel, group: string, public hasMotionType: boolean, public settings: Setting[]) {
|
||||
constructor(public plugin: ObjectDetectionPlugin, mixinDevice: VideoCamera & Camera & MotionSensor & ObjectDetector & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string, public objectDetection: ObjectDetection & ScryptedDevice, public model: ObjectDetectionModel, group: string, public hasMotionType: boolean) {
|
||||
super({
|
||||
mixinDevice, mixinDeviceState,
|
||||
mixinProviderNativeId: providerNativeId,
|
||||
@@ -157,11 +158,12 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
}
|
||||
|
||||
getCurrentSettings() {
|
||||
if (!this.settings)
|
||||
const settings = this.model.settings;
|
||||
if (!settings)
|
||||
return;
|
||||
|
||||
const ret: { [key: string]: any } = {};
|
||||
for (const setting of this.settings) {
|
||||
for (const setting of settings) {
|
||||
let value: any;
|
||||
if (setting.multiple) {
|
||||
value = safeParseJson(this.storage.getItem(setting.key));
|
||||
@@ -209,8 +211,6 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
}
|
||||
|
||||
async register() {
|
||||
const model = await this.objectDetection.getDetectionModel();
|
||||
|
||||
if (!this.hasMotionType) {
|
||||
this.motionListener = this.cameraDevice.listen(ScryptedInterface.MotionSensor, async () => {
|
||||
if (!this.cameraDevice.motionDetected) {
|
||||
@@ -300,6 +300,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
async runPipelineAnalysisLoop(signal: Deferred<void>, options: {
|
||||
suppress?: boolean,
|
||||
}) {
|
||||
await this.updateModel();
|
||||
while (!signal.finished) {
|
||||
if (options.suppress) {
|
||||
this.console.log('Resuming motion processing after active motion timeout.');
|
||||
@@ -536,8 +537,9 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
match = polygonOverlap(box, zoneValue);
|
||||
}
|
||||
|
||||
if (match && zoneInfo?.classes?.length) {
|
||||
match = zoneInfo.classes.includes(o.className);
|
||||
const classes = zoneInfo?.classes?.length ? zoneInfo?.classes : this.model?.classes || [];
|
||||
if (match && classes.length) {
|
||||
match = classes.includes(o.className);
|
||||
}
|
||||
if (match) {
|
||||
o.zones.push(zone);
|
||||
@@ -611,7 +613,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
const ret = await this.getNativeObjectTypes();
|
||||
if (!ret.classes)
|
||||
ret.classes = [];
|
||||
ret.classes.push(...(await this.objectDetection.getDetectionModel()).classes);
|
||||
ret.classes.push(...(await this.objectDetection.getDetectionModel(this.getCurrentSettings())).classes);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -656,17 +658,22 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
return use.id;
|
||||
}
|
||||
|
||||
async getMixinSettings(): Promise<Setting[]> {
|
||||
const settings: Setting[] = [];
|
||||
|
||||
async updateModel() {
|
||||
try {
|
||||
this.settings = (await this.objectDetection.getDetectionModel(this.getCurrentSettings())).settings;
|
||||
this.model = await this.objectDetection.getDetectionModel(this.getCurrentSettings());
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.settings) {
|
||||
settings.push(...this.settings.map(setting => {
|
||||
async getMixinSettings(): Promise<Setting[]> {
|
||||
const settings: Setting[] = [];
|
||||
|
||||
await this.updateModel();
|
||||
const modelSettings = this.model.settings;
|
||||
|
||||
if (modelSettings) {
|
||||
settings.push(...modelSettings.map(setting => {
|
||||
let value: any;
|
||||
if (setting.multiple) {
|
||||
value = safeParseJson(this.storage.getItem(setting.key));
|
||||
@@ -737,14 +744,15 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
});
|
||||
|
||||
if (!this.hasMotionType) {
|
||||
const classes = this.model.classes;
|
||||
settings.push(
|
||||
{
|
||||
subgroup,
|
||||
key: `zoneinfo-classes-${name}`,
|
||||
title: `Detection Classes`,
|
||||
description: 'The detection classes to match inside this zone. An empty list will match all classes.',
|
||||
choices: (await this.getObjectTypes())?.classes || [],
|
||||
value: zi?.classes || [],
|
||||
description: 'The detection classes to match inside this zone.',
|
||||
choices: classes || [],
|
||||
value: zi?.classes?.length ? zi?.classes : classes || [],
|
||||
multiple: true,
|
||||
},
|
||||
);
|
||||
@@ -817,7 +825,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
if (value && this.settings?.find(s => s.key === key)?.multiple) {
|
||||
if (value && this.model.settings?.find(s => s.key === key)?.multiple) {
|
||||
vs = JSON.stringify(value);
|
||||
}
|
||||
|
||||
@@ -895,9 +903,7 @@ class ObjectDetectorMixin extends MixinDeviceBase<ObjectDetection> implements Mi
|
||||
const group = hasMotionType ? 'Motion Detection' : 'Object Detection';
|
||||
// const group = objectDetection.name.replace('Plugin', '').trim();
|
||||
|
||||
const settings = this.model.settings;
|
||||
|
||||
const ret = new ObjectDetectionMixin(this.plugin, mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.mixinProviderNativeId, objectDetection, this.model, group, hasMotionType, settings);
|
||||
const ret = new ObjectDetectionMixin(this.plugin, mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.mixinProviderNativeId, objectDetection, this.model, group, hasMotionType);
|
||||
this.currentMixins.add(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ fs
|
||||
src
|
||||
.vscode
|
||||
dist/*.js
|
||||
onvif
|
||||
|
||||
2
plugins/onvif/.vscode/launch.json
vendored
2
plugins/onvif/.vscode/launch.json
vendored
@@ -10,7 +10,7 @@
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"**/plugin-remote-worker.*",
|
||||
"**/plugin-console.*",
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
|
||||
1
plugins/onvif/onvif
Submodule
1
plugins/onvif/onvif
Submodule
Submodule plugins/onvif/onvif added at 5641b572c4
4041
plugins/onvif/package-lock.json
generated
4041
plugins/onvif/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/onvif",
|
||||
"version": "0.0.127",
|
||||
"version": "0.1.5",
|
||||
"description": "ONVIF Camera Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -36,13 +36,12 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"base-64": "^1.0.0",
|
||||
"http-auth-utils": "^4.0.0",
|
||||
"md5": "^2.3.0",
|
||||
"onvif": "^0.6.8",
|
||||
"onvif": "file:./onvif",
|
||||
"xml2js": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { AuthFetchCredentialState, AuthFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { EventEmitter } from 'events';
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import https from 'https';
|
||||
import https, { RequestOptions } from 'https';
|
||||
import { Readable } from 'stream';
|
||||
import { FetchParser, HttpFetchOptions, HttpFetchResponseType } from '../../../server/src/http-fetch-helpers';
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
@@ -58,15 +60,29 @@ export class OnvifCameraAPI {
|
||||
rtspUrls = new Map<string, string>();
|
||||
profiles: Promise<any>;
|
||||
binaryStateEvent: string;
|
||||
digestAuth: AxiosDigestAuth;
|
||||
credential: AuthFetchCredentialState;
|
||||
detections: Map<string, string>;
|
||||
|
||||
constructor(public cam: any, username: string, password: string, public console: Console, binaryStateEvent: string) {
|
||||
this.binaryStateEvent = binaryStateEvent
|
||||
this.digestAuth = new AxiosDigestAuth({
|
||||
this.credential = {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async request<T extends HttpFetchResponseType>(urlOrOptions: string | URL | HttpFetchOptions<T>, body?: Readable) {
|
||||
const options: AuthFetchOptions<T> = {
|
||||
...typeof urlOrOptions !== 'string' && !(urlOrOptions instanceof URL) ? urlOrOptions : {
|
||||
url: urlOrOptions,
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
credential: this.credential,
|
||||
body,
|
||||
};
|
||||
|
||||
const response = await authHttpFetch(options);
|
||||
return response;
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
@@ -88,67 +104,68 @@ export class OnvifCameraAPI {
|
||||
this.cam.on('event', (event: any, xml: string) => {
|
||||
ret.emit('data', xml);
|
||||
|
||||
if (!event.message.message.data?.simpleItem?.$)
|
||||
return;
|
||||
|
||||
const dataValue = event.message.message.data.simpleItem.$.Value;
|
||||
const eventTopic = stripNamespaces(event.topic._);
|
||||
|
||||
if (event.message.message.data && event.message.message.data.simpleItem) {
|
||||
const dataValue = event.message.message.data.simpleItem.$.Value;
|
||||
if (eventTopic.includes('MotionAlarm')) {
|
||||
// ret.emit('event', OnvifEvent.MotionBuggy);
|
||||
if (dataValue)
|
||||
ret.emit('event', OnvifEvent.MotionStart)
|
||||
else
|
||||
ret.emit('event', OnvifEvent.MotionStop)
|
||||
if (eventTopic.includes('MotionAlarm')) {
|
||||
// ret.emit('event', OnvifEvent.MotionBuggy);
|
||||
if (dataValue)
|
||||
ret.emit('event', OnvifEvent.MotionStart)
|
||||
else
|
||||
ret.emit('event', OnvifEvent.MotionStop)
|
||||
}
|
||||
else if (eventTopic.includes('DetectedSound')) {
|
||||
if (dataValue)
|
||||
ret.emit('event', OnvifEvent.AudioStart)
|
||||
else
|
||||
ret.emit('event', OnvifEvent.AudioStop)
|
||||
}
|
||||
// Reolink
|
||||
else if (eventTopic.includes('Visitor') && (dataValue === true || dataValue === false)) {
|
||||
if (dataValue) {
|
||||
ret.emit('event', OnvifEvent.BinaryStart)
|
||||
}
|
||||
else if (eventTopic.includes('DetectedSound')) {
|
||||
if (dataValue)
|
||||
ret.emit('event', OnvifEvent.AudioStart)
|
||||
else
|
||||
ret.emit('event', OnvifEvent.AudioStop)
|
||||
else {
|
||||
ret.emit('event', OnvifEvent.BinaryStop)
|
||||
}
|
||||
// Reolink
|
||||
else if (eventTopic.includes('Visitor') && (dataValue === true || dataValue === false)) {
|
||||
if (dataValue) {
|
||||
ret.emit('event', OnvifEvent.BinaryStart)
|
||||
}
|
||||
// Mobotix T26
|
||||
else if (eventTopic.includes('VideoSource/Alarm')) {
|
||||
if (dataValue === "Ring" || dataValue === "CameraBellButton") {
|
||||
ret.emit('event', OnvifEvent.BinaryRingEvent);
|
||||
}
|
||||
}
|
||||
// else if (eventTopic.includes('DigitalInput')) {
|
||||
// if (dataValue)
|
||||
// ret.emit('event', OnvifEvent.BinaryStart)
|
||||
// else
|
||||
// ret.emit('event', OnvifEvent.BinaryStop)
|
||||
// }
|
||||
else if (this.binaryStateEvent && eventTopic.includes(this.binaryStateEvent)) {
|
||||
if (dataValue)
|
||||
ret.emit('event', OnvifEvent.BinaryStart)
|
||||
else
|
||||
ret.emit('event', OnvifEvent.BinaryStop)
|
||||
}
|
||||
else if (eventTopic.includes('RuleEngine/CellMotionDetector/Motion')) {
|
||||
// unclear if the IsMotion false is indicative of motion stop?
|
||||
if (event.message.message.data.simpleItem.$.Name === 'IsMotion' && dataValue) {
|
||||
ret.emit('event', OnvifEvent.MotionBuggy);
|
||||
}
|
||||
}
|
||||
else if (eventTopic.includes('RuleEngine/ObjectDetector')) {
|
||||
if (dataValue) {
|
||||
try {
|
||||
const eventName = event.message.message.data.simpleItem.$.Name;
|
||||
const className = this.detections.get(eventName);
|
||||
this.console.log('object detected:', className);
|
||||
ret.emit('event', OnvifEvent.Detection, className);
|
||||
}
|
||||
else {
|
||||
ret.emit('event', OnvifEvent.BinaryStop)
|
||||
}
|
||||
}
|
||||
// Mobotix T26
|
||||
else if (eventTopic.includes('VideoSource/Alarm')) {
|
||||
if (dataValue === "Ring" || dataValue === "CameraBellButton") {
|
||||
ret.emit('event', OnvifEvent.BinaryRingEvent);
|
||||
}
|
||||
}
|
||||
// else if (eventTopic.includes('DigitalInput')) {
|
||||
// if (dataValue)
|
||||
// ret.emit('event', OnvifEvent.BinaryStart)
|
||||
// else
|
||||
// ret.emit('event', OnvifEvent.BinaryStop)
|
||||
// }
|
||||
else if (this.binaryStateEvent && eventTopic.includes(this.binaryStateEvent)) {
|
||||
if (dataValue)
|
||||
ret.emit('event', OnvifEvent.BinaryStart)
|
||||
else
|
||||
ret.emit('event', OnvifEvent.BinaryStop)
|
||||
}
|
||||
else if (eventTopic.includes('RuleEngine/CellMotionDetector/Motion')) {
|
||||
// unclear if the IsMotion false is indicative of motion stop?
|
||||
if (event.message.message.data.simpleItem.$.Name === 'IsMotion' && dataValue) {
|
||||
ret.emit('event', OnvifEvent.MotionBuggy);
|
||||
}
|
||||
}
|
||||
else if (eventTopic.includes('RuleEngine/ObjectDetector')) {
|
||||
if (dataValue) {
|
||||
try {
|
||||
const eventName = event.message.message.data.simpleItem.$.Name;
|
||||
const className = this.detections.get(eventName);
|
||||
this.console.log('object detected:', className);
|
||||
ret.emit('event', OnvifEvent.Detection, className);
|
||||
}
|
||||
catch (e) {
|
||||
this.console.warn('error parsing detection', e);
|
||||
}
|
||||
catch (e) {
|
||||
this.console.warn('error parsing detection', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,13 +245,14 @@ export class OnvifCameraAPI {
|
||||
this.console.log(xml);
|
||||
this.detections = new Map();
|
||||
try {
|
||||
|
||||
for (const [className, entry] of Object.entries(data.topicSet.ruleEngine.objectDetector) as any) {
|
||||
try {
|
||||
const eventName = entry.messageDescription.data.simpleItemDescription.$.Name;
|
||||
this.detections.set(eventName, className);
|
||||
}
|
||||
catch (e) {
|
||||
if (data.topicSet.ruleEngine.objectDetector) {
|
||||
for (const [className, entry] of Object.entries(data.topicSet.ruleEngine.objectDetector) as any) {
|
||||
try {
|
||||
const eventName = entry.messageDescription.data.simpleItemDescription.$.Name;
|
||||
this.detections.set(eventName, className);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,15 +297,12 @@ export class OnvifCameraAPI {
|
||||
if (!snapshotUri)
|
||||
return;
|
||||
|
||||
const response = await this.digestAuth.request({
|
||||
method: 'GET',
|
||||
const response = await this.request({
|
||||
url: snapshotUri,
|
||||
responseType: 'arraybuffer',
|
||||
httpsAgent,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
return Buffer.from(response.data);
|
||||
return response.body;
|
||||
}
|
||||
|
||||
getDeviceInformation(): Promise<any> {
|
||||
|
||||
@@ -2,10 +2,16 @@ import { ObjectsDetected, ScryptedDevice, ScryptedDeviceBase, ScryptedInterface
|
||||
import { OnvifCameraAPI, OnvifEvent } from "./onvif-api";
|
||||
import { Destroyable } from "../../rtsp/src/rtsp";
|
||||
|
||||
export async function listenEvents(thisDevice: ScryptedDeviceBase, client: OnvifCameraAPI) {
|
||||
export async function listenEvents(thisDevice: ScryptedDeviceBase, client: OnvifCameraAPI, motionTimeoutMs = 30000) {
|
||||
let motionTimeout: NodeJS.Timeout;
|
||||
let binaryTimeout: NodeJS.Timeout;
|
||||
|
||||
const triggerMotion = () => {
|
||||
thisDevice.motionDetected = true;
|
||||
clearTimeout(motionTimeout);
|
||||
motionTimeout = setTimeout(() => thisDevice.motionDetected = false, motionTimeoutMs);
|
||||
};
|
||||
|
||||
try {
|
||||
await client.supportsEvents();
|
||||
}
|
||||
@@ -17,22 +23,32 @@ export async function listenEvents(thisDevice: ScryptedDeviceBase, client: Onvif
|
||||
const events = client.listenEvents();
|
||||
events.on('event', (event, className) => {
|
||||
if (event === OnvifEvent.MotionBuggy) {
|
||||
thisDevice.motionDetected = true;
|
||||
clearTimeout(motionTimeout);
|
||||
motionTimeout = setTimeout(() => thisDevice.motionDetected = false, 30000);
|
||||
// some onvif cameras have motion with no associated motion end event.
|
||||
triggerMotion();
|
||||
return;
|
||||
}
|
||||
if (event === OnvifEvent.BinaryRingEvent) {
|
||||
thisDevice.binaryState = true;
|
||||
clearTimeout(binaryTimeout);
|
||||
binaryTimeout = setTimeout(() => thisDevice.binaryState = false, 30000);
|
||||
binaryTimeout = setTimeout(() => thisDevice.binaryState = false, motionTimeoutMs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event === OnvifEvent.MotionStart)
|
||||
thisDevice.motionDetected = true;
|
||||
else if (event === OnvifEvent.MotionStop)
|
||||
thisDevice.motionDetected = false;
|
||||
if (event === OnvifEvent.MotionStart) {
|
||||
// some onvif cameras (like the reolink doorbell) have very short duration motion
|
||||
// events.
|
||||
// furthermore, cameras are not guaranteed to send motion stop events, which makes.
|
||||
// for the sake of providing normalized motion durations through scrypted, debounce the motion.
|
||||
triggerMotion();
|
||||
// thisDevice.motionDetected = true;
|
||||
}
|
||||
else if (event === OnvifEvent.MotionStop) {
|
||||
// reset the trigger to debounce per above.
|
||||
if (thisDevice.motionDetected)
|
||||
triggerMotion();
|
||||
|
||||
// thisDevice.motionDetected = false;
|
||||
}
|
||||
else if (event === OnvifEvent.AudioStart)
|
||||
thisDevice.audioDetected = true;
|
||||
else if (event === OnvifEvent.AudioStop)
|
||||
@@ -55,8 +71,9 @@ export async function listenEvents(thisDevice: ScryptedDeviceBase, client: Onvif
|
||||
}
|
||||
});
|
||||
|
||||
const ret: Destroyable = {
|
||||
const ret = {
|
||||
destroy() {
|
||||
clearTimeout(motionTimeout);
|
||||
try {
|
||||
client.unsubscribe();
|
||||
}
|
||||
@@ -70,6 +87,7 @@ export async function listenEvents(thisDevice: ScryptedDeviceBase, client: Onvif
|
||||
emit(eventName: string | symbol, ...args: any[]) {
|
||||
return events.emit(eventName, ...args);
|
||||
},
|
||||
triggerMotion,
|
||||
};
|
||||
|
||||
return ret;
|
||||
|
||||
8
plugins/opencv/.vscode/settings.json
vendored
8
plugins/opencv/.vscode/settings.json
vendored
@@ -5,12 +5,12 @@
|
||||
// "scrypted.serverRoot": "/home/pi/.scrypted",
|
||||
|
||||
// docker installation
|
||||
"scrypted.debugHost": "koushik-ubuntu",
|
||||
"scrypted.serverRoot": "/server",
|
||||
// "scrypted.debugHost": "koushik-ubuntu",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
|
||||
// local checkout
|
||||
// "scrypted.debugHost": "127.0.0.1",
|
||||
// "scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "koushik-windows",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
|
||||
4
plugins/opencv/package-lock.json
generated
4
plugins/opencv/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.89",
|
||||
"version": "0.0.90",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.89",
|
||||
"version": "0.0.90",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.89"
|
||||
"version": "0.0.90"
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@ pillow-simd; sys_platform == 'linux' and platform_machine == 'x86_64'
|
||||
imutils>=0.5.0
|
||||
# opencv-python is not available on armhf
|
||||
# locked to version because 4.8.0.76 is broken.
|
||||
opencv-python==4.8.0.74; sys_platform != 'linux' or platform_machine == 'x86_64'
|
||||
opencv-python==4.8.0.74; sys_platform != 'linux' or platform_machine == 'x86_64' or platform_machine == 'aarch64'
|
||||
|
||||
10
plugins/openvino/.vscode/settings.json
vendored
10
plugins/openvino/.vscode/settings.json
vendored
@@ -1,17 +1,17 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "koushik-ubuntu",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
"scrypted.debugHost": "koushik-ubuntu",
|
||||
"scrypted.serverRoot": "/server",
|
||||
|
||||
// pi local installation
|
||||
// "scrypted.debugHost": "192.168.2.119",
|
||||
// "scrypted.serverRoot": "/home/pi/.scrypted",
|
||||
|
||||
// local checkout
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "koushik-windows",
|
||||
// "scrypted.debugHost": "127.0.0.1",
|
||||
// "scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// // "scrypted.debugHost": "koushik-windows",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
|
||||
4
plugins/openvino/package-lock.json
generated
4
plugins/openvino/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.45",
|
||||
"version": "0.1.48",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.45",
|
||||
"version": "0.1.48",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
"scrypted": {
|
||||
"name": "OpenVINO Object Detection",
|
||||
"pluginDependencies": [
|
||||
"@scrypted/objectdetector",
|
||||
"@scrypted/python-codecs"
|
||||
"@scrypted/objectdetector"
|
||||
],
|
||||
"runtime": "python",
|
||||
"type": "API",
|
||||
@@ -42,5 +41,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.45"
|
||||
"version": "0.1.48"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
openvino==2023.0.2
|
||||
openvino==2023.2.0
|
||||
|
||||
# pillow for anything not intel linux, pillow-simd is available on x64 linux
|
||||
# pillow-simd is available on x64 linux
|
||||
# pillow-simd confirmed not building with arm64 linux or apple silicon
|
||||
Pillow>=5.4.1; sys_platform != 'linux' or platform_machine != 'x86_64'
|
||||
pillow-simd; sys_platform == 'linux' and platform_machine == 'x86_64'
|
||||
|
||||
4
plugins/prebuffer-mixin/package-lock.json
generated
4
plugins/prebuffer-mixin/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.8",
|
||||
"version": "0.10.11",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.8",
|
||||
"version": "0.10.11",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.8",
|
||||
"version": "0.10.11",
|
||||
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -469,7 +469,8 @@ class PrebufferSession {
|
||||
this.usingScryptedParser = true;
|
||||
this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser')
|
||||
const json = await mediaManager.convertMediaObjectToJSON<any>(mo, 'x-scrypted/x-rfc4571');
|
||||
const { url, sdp, mediaStreamOptions } = json;
|
||||
let { url, sdp, mediaStreamOptions } = json;
|
||||
sdp = addTrackControls(sdp);
|
||||
sessionMso = mediaStreamOptions;
|
||||
|
||||
const rtspParser = createRtspParser();
|
||||
|
||||
@@ -176,7 +176,8 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
|
||||
}
|
||||
|
||||
events.emit('rtsp', chunk);
|
||||
resetActivityTimer();
|
||||
if (chunk.type === inputVideoCodec)
|
||||
resetActivityTimer();
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
|
||||
227
plugins/reolink/package-lock.json
generated
227
plugins/reolink/package-lock.json
generated
@@ -1,20 +1,17 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.57",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.57",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/multiparty": "^0.0.33",
|
||||
"multiparty": "^4.2.3",
|
||||
"onvif": "^0.6.8"
|
||||
"onvif": "file:../onvif/onvif"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.18"
|
||||
@@ -29,15 +26,16 @@
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -72,13 +70,27 @@
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.6.tgz",
|
||||
"integrity": "sha512-e/XKs7/BYpPQkces0Cm4dUmhT9hR0rjvnNZAVRyRnNWdQ8cyCMFWS9HIrMWOdzAocKDNBXi1vKjJ8CywrW5xgQ==",
|
||||
"../onvif/onvif": {
|
||||
"version": "0.7.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
"lodash.get": "^4.4.2",
|
||||
"xml2js": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coveralls": "^3.1.1",
|
||||
"dot": "^1.1.3",
|
||||
"eslint": "^8.3.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"ip": "^1.1.5",
|
||||
"keypress": "^0.2.1",
|
||||
"mocha": "^10.0.0",
|
||||
"mocha-lcov-reporter": "^1.3.0",
|
||||
"nimble": "^0.0.2",
|
||||
"nyc": "^15.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
@@ -89,192 +101,15 @@
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/multiparty": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-0.0.33.tgz",
|
||||
"integrity": "sha512-Il6cJUpSqgojT7NxbVJUvXkCblm50/yEJYtblISDsNIeNYf4yMAhdizzidUk6h8pJ8yhwK/3Fkb+3Dwcgtwl8w==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.16.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz",
|
||||
"integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw=="
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
|
||||
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
|
||||
"dependencies": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
|
||||
},
|
||||
"node_modules/multiparty": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.3.tgz",
|
||||
"integrity": "sha512-Ak6EUJZuhGS8hJ3c2fY6UW5MbkGUPMBEGd13djUzoY/BHqV/gTuFWtC6IuVA7A2+v3yjBS6c4or50xhzTQZImQ==",
|
||||
"dependencies": {
|
||||
"http-errors": "~1.8.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
"integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/onvif": {
|
||||
"version": "0.6.9",
|
||||
"resolved": "https://registry.npmjs.org/onvif/-/onvif-0.6.9.tgz",
|
||||
"integrity": "sha512-aKr14CG8dkHMEF3bUqBZA1OdZi4ffzfmR5E1Y3v4WpweCGkywERAQDhQM3PRUvLNtqnWbcDEcq4l7gBSZ7JCyA==",
|
||||
"dependencies": {
|
||||
"lodash.get": "^4.4.2",
|
||||
"xml2js": "^0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
"resolved": "../onvif/onvif",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.57",
|
||||
"description": "Reolink Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -35,12 +35,9 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/multiparty": "^0.0.33",
|
||||
"multiparty": "^4.2.3",
|
||||
"onvif": "^0.6.8"
|
||||
"onvif": "file:../onvif/onvif"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.18"
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
import { Camera, DeviceCreatorSettings, DeviceInformation, Intercom, MediaObject, PictureOptions, Reboot, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, Intercom, MediaObject, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, PanTiltZoom, PanTiltZoomCommand, PictureOptions, Reboot, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import { EventEmitter } from "stream";
|
||||
import { Destroyable, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
|
||||
import { OnvifCameraAPI, connectCameraAPI } from './onvif-api';
|
||||
import { listenEvents } from './onvif-events';
|
||||
import { OnvifIntercom } from './onvif-intercom';
|
||||
import { DevInfo, Enc, ReolinkCameraClient } from './reolink-api';
|
||||
import { AIState, DevInfo, Enc, ReolinkCameraClient } from './reolink-api';
|
||||
|
||||
class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom {
|
||||
class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom, ObjectDetector, PanTiltZoom {
|
||||
client: ReolinkCameraClient;
|
||||
onvifClient: OnvifCameraAPI;
|
||||
onvifIntercom = new OnvifIntercom(this);
|
||||
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
|
||||
motionTimeout: NodeJS.Timeout;
|
||||
|
||||
storageSettings = new StorageSettings(this, {
|
||||
doorbell: {
|
||||
@@ -25,6 +26,29 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
title: 'RTMP Port Override',
|
||||
placeholder: '1935',
|
||||
type: 'number',
|
||||
},
|
||||
motionTimeout: {
|
||||
group: 'Advanced',
|
||||
title: 'Motion Timeout',
|
||||
defaultValue: 20,
|
||||
type: 'number',
|
||||
},
|
||||
hasObjectDetector: {
|
||||
json: true,
|
||||
hide: true,
|
||||
},
|
||||
ptz: {
|
||||
title: 'PTZ Capabilities',
|
||||
choices: [
|
||||
'Pan',
|
||||
'Tilt',
|
||||
'Zoom',
|
||||
],
|
||||
multiple: true,
|
||||
onPut: async () => {
|
||||
await this.updateDevice();
|
||||
this.updatePtzCaps();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -33,6 +57,49 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
|
||||
this.updateDeviceInfo();
|
||||
this.updateDevice();
|
||||
|
||||
this.updatePtzCaps();
|
||||
}
|
||||
|
||||
updatePtzCaps() {
|
||||
const { ptz } = this.storageSettings.values;
|
||||
this.ptzCapabilities = {
|
||||
pan: ptz?.includes('Pan'),
|
||||
tilt: ptz?.includes('Tilt'),
|
||||
zoom: ptz?.includes('Zoom'),
|
||||
}
|
||||
}
|
||||
|
||||
async getDetectionInput(detectionId: string, eventId?: any): Promise<MediaObject> {
|
||||
return;
|
||||
}
|
||||
|
||||
async ptzCommand(command: PanTiltZoomCommand): Promise<void> {
|
||||
const client = this.getClient();
|
||||
client.ptz(command);
|
||||
}
|
||||
|
||||
async getObjectTypes(): Promise<ObjectDetectionTypes> {
|
||||
try {
|
||||
const ai: AIState = this.storageSettings.values.hasObjectDetector[0]?.value;
|
||||
const classes: string[] = [];
|
||||
|
||||
for (const key of Object.keys(ai)) {
|
||||
if (key === 'channel')
|
||||
continue;
|
||||
const { alarm_state, support } = ai[key];
|
||||
if (support)
|
||||
classes.push(key);
|
||||
}
|
||||
return {
|
||||
classes,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
return {
|
||||
classes: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async startIntercom(media: MediaObject): Promise<void> {
|
||||
@@ -48,7 +115,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
return this.onvifIntercom.stopIntercom();
|
||||
}
|
||||
|
||||
updateDevice() {
|
||||
async updateDevice() {
|
||||
const interfaces = this.provider.getInterfaces();
|
||||
let type = ScryptedDeviceType.Camera;
|
||||
let name = 'Reolink Camera';
|
||||
@@ -60,7 +127,13 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
type = ScryptedDeviceType.Doorbell;
|
||||
name = 'Reolink Doorbell';
|
||||
}
|
||||
this.provider.updateDevice(this.nativeId, name, interfaces, type);
|
||||
if (this.storageSettings.values.ptz?.length) {
|
||||
interfaces.push(ScryptedInterface.PanTiltZoom);
|
||||
}
|
||||
if (this.storageSettings.values.hasObjectDetector) {
|
||||
interfaces.push(ScryptedInterface.ObjectDetector);
|
||||
}
|
||||
await this.provider.updateDevice(this.nativeId, name, interfaces, type);
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
@@ -97,11 +170,70 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
}
|
||||
|
||||
async listenEvents() {
|
||||
if (this.storageSettings.values.doorbell)
|
||||
return listenEvents(this, await this.createOnvifClient());
|
||||
|
||||
const client = this.getClient();
|
||||
let killed = false;
|
||||
const client = this.getClient();
|
||||
|
||||
// reolink ai might not trigger motion if objects are detected, weird.
|
||||
const startAI = async (ret: Destroyable, triggerMotion: () => void) => {
|
||||
let hasSucceeded = false;
|
||||
while (!killed) {
|
||||
try {
|
||||
const ai = await client.getAiState();
|
||||
ret.emit('data', JSON.stringify(ai.data));
|
||||
|
||||
const classes: string[] = [];
|
||||
|
||||
for (const key of Object.keys(ai.value)) {
|
||||
if (key === 'channel')
|
||||
continue;
|
||||
const { alarm_state, support } = ai.value[key];
|
||||
if (support)
|
||||
classes.push(key);
|
||||
}
|
||||
|
||||
if (!classes.length)
|
||||
return;
|
||||
|
||||
hasSucceeded = true;
|
||||
if (!this.storageSettings.values.hasObjectDetector) {
|
||||
this.storageSettings.values.hasObjectDetector = ai.data;
|
||||
this.updateDevice();
|
||||
}
|
||||
const od: ObjectsDetected = {
|
||||
timestamp: Date.now(),
|
||||
detections: [],
|
||||
};
|
||||
for (const c of classes) {
|
||||
const { alarm_state } = ai.value[c];
|
||||
if (alarm_state) {
|
||||
od.detections.push({
|
||||
className: c,
|
||||
score: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (od.detections.length) {
|
||||
triggerMotion();
|
||||
sdk.deviceManager.onDeviceEvent(this.nativeId, ScryptedInterface.ObjectDetector, od);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (!hasSucceeded)
|
||||
return;
|
||||
ret.emit('error', e);
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.storageSettings.values.doorbell) {
|
||||
const ret = await listenEvents(this, await this.createOnvifClient(), this.storageSettings.values.motionTimeout * 1000);
|
||||
ret.on('close', () => killed = true);
|
||||
ret.on('error', () => killed = true);
|
||||
startAI(ret, ret.triggerMotion);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const events = new EventEmitter();
|
||||
const ret: Destroyable = {
|
||||
on: function (eventName: string | symbol, listener: (...args: any[]) => void): void {
|
||||
@@ -115,13 +247,17 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
}
|
||||
};
|
||||
|
||||
const triggerMotion = () => {
|
||||
this.motionDetected = true;
|
||||
clearTimeout(this.motionTimeout);
|
||||
this.motionTimeout = setTimeout(() => this.motionDetected = false, this.storageSettings.values.motionTimeout * 1000);
|
||||
};
|
||||
(async () => {
|
||||
while (!killed) {
|
||||
try {
|
||||
// const ai = await client.getAiState();
|
||||
// ret.emit('data', JSON.stringify(ai));
|
||||
const { value, data } = await client.getMotionState();
|
||||
this.motionDetected = value;
|
||||
if (value)
|
||||
triggerMotion();
|
||||
ret.emit('data', JSON.stringify(data));
|
||||
}
|
||||
catch (e) {
|
||||
@@ -130,6 +266,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
await sleep(1000);
|
||||
}
|
||||
})();
|
||||
startAI(ret, triggerMotion);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -228,7 +365,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
}
|
||||
];
|
||||
|
||||
if (deviceInfo?.model == "Reolink TrackMix PoE"){
|
||||
if (deviceInfo?.model == "Reolink TrackMix PoE") {
|
||||
streams.push({
|
||||
name: '',
|
||||
id: 'autotrack.bcs',
|
||||
@@ -283,12 +420,17 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
|
||||
}
|
||||
|
||||
return streams;
|
||||
|
||||
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: string) {
|
||||
this.client = undefined;
|
||||
super.putSetting(key, value);
|
||||
if (this.storageSettings.keys[key]) {
|
||||
await this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
else {
|
||||
await super.putSetting(key, value);
|
||||
}
|
||||
this.updateDevice();
|
||||
this.updateDeviceInfo();
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import AxiosDigestAuth from "@koush/axios-digest-auth";
|
||||
import { AuthFetchCredentialState, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import https from 'https';
|
||||
|
||||
export const reolinkHttpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
export async function getMotionState(digestAuth: AxiosDigestAuth, username: string, password: string, address: string, channelId: number) {
|
||||
export async function getMotionState(credential: AuthFetchCredentialState, username: string, password: string, address: string, channelId: number) {
|
||||
const url = new URL(`http://${address}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'GetMdState');
|
||||
params.set('channel', channelId.toString());
|
||||
params.set('user', username);
|
||||
params.set('password', password);
|
||||
const response = await digestAuth.request({
|
||||
const response = await authHttpFetch({
|
||||
credential,
|
||||
url: url.toString(),
|
||||
httpsAgent: reolinkHttpsAgent,
|
||||
rejectUnauthorized: false,
|
||||
responseType: 'json',
|
||||
});
|
||||
return {
|
||||
value: !!response.data?.[0]?.value?.state,
|
||||
data: response.data,
|
||||
value: !!response.body?.[0]?.value?.state,
|
||||
data: response.body,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,54 +1,86 @@
|
||||
import AxiosDigestAuth from "@koush/axios-digest-auth";
|
||||
import { AuthFetchCredentialState, AuthFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { EventEmitter } from 'events';
|
||||
import https, { RequestOptions } from 'https';
|
||||
import { PassThrough, Readable } from 'stream';
|
||||
import { HttpFetchOptions, HttpFetchResponseType } from '../../../server/src/http-fetch-helpers';
|
||||
|
||||
import { getMotionState, reolinkHttpsAgent } from './probe';
|
||||
import { PanTiltZoomCommand } from "@scrypted/sdk";
|
||||
import { sleep } from "@scrypted/common/src/sleep";
|
||||
|
||||
export interface Enc {
|
||||
audio: number;
|
||||
channel: number;
|
||||
audio: number;
|
||||
channel: number;
|
||||
mainStream: Stream;
|
||||
subStream: Stream;
|
||||
subStream: Stream;
|
||||
}
|
||||
|
||||
export interface Stream {
|
||||
bitRate: number;
|
||||
bitRate: number;
|
||||
frameRate: number;
|
||||
gop: number;
|
||||
height: number;
|
||||
profile: string;
|
||||
size: string;
|
||||
vType: string;
|
||||
width: number;
|
||||
gop: number;
|
||||
height: number;
|
||||
profile: string;
|
||||
size: string;
|
||||
vType: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export interface DevInfo {
|
||||
B485: number;
|
||||
IOInputNum: number;
|
||||
IOOutputNum: number;
|
||||
audioNum: number;
|
||||
buildDay: string;
|
||||
cfgVer: string;
|
||||
channelNum: number;
|
||||
detail: string;
|
||||
diskNum: number;
|
||||
exactType: string;
|
||||
firmVer: string;
|
||||
B485: number;
|
||||
IOInputNum: number;
|
||||
IOOutputNum: number;
|
||||
audioNum: number;
|
||||
buildDay: string;
|
||||
cfgVer: string;
|
||||
channelNum: number;
|
||||
detail: string;
|
||||
diskNum: number;
|
||||
exactType: string;
|
||||
firmVer: string;
|
||||
frameworkVer: number;
|
||||
hardVer: string;
|
||||
model: string;
|
||||
name: string;
|
||||
pakSuffix: string;
|
||||
serial: string;
|
||||
type: string;
|
||||
wifi: number;
|
||||
hardVer: string;
|
||||
model: string;
|
||||
name: string;
|
||||
pakSuffix: string;
|
||||
serial: string;
|
||||
type: string;
|
||||
wifi: number;
|
||||
}
|
||||
|
||||
export interface AIDetectionState {
|
||||
alarm_state: number;
|
||||
support: number;
|
||||
}
|
||||
|
||||
export type AIState = {
|
||||
[key: string]: AIDetectionState;
|
||||
} & {
|
||||
channel: number;
|
||||
};
|
||||
|
||||
export class ReolinkCameraClient {
|
||||
digestAuth: AxiosDigestAuth;
|
||||
credential: AuthFetchCredentialState;
|
||||
|
||||
constructor(public host: string, public username: string, public password: string, public channelId: number, public console: Console) {
|
||||
this.digestAuth = new AxiosDigestAuth({
|
||||
password,
|
||||
this.credential = {
|
||||
username,
|
||||
});
|
||||
password,
|
||||
};
|
||||
}
|
||||
|
||||
async request<T extends HttpFetchResponseType>(urlOrOptions: string | URL | HttpFetchOptions<T>, body?: Readable) {
|
||||
const options: AuthFetchOptions<T> = {
|
||||
...typeof urlOrOptions !== 'string' && !(urlOrOptions instanceof URL) ? urlOrOptions : {
|
||||
url: urlOrOptions,
|
||||
},
|
||||
rejectUnauthorized: false,
|
||||
credential: this.credential,
|
||||
body,
|
||||
};
|
||||
|
||||
const response = await authHttpFetch(options);
|
||||
return response;
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
@@ -57,13 +89,13 @@ export class ReolinkCameraClient {
|
||||
params.set('cmd', 'Reboot');
|
||||
params.set('user', this.username);
|
||||
params.set('password', this.password);
|
||||
const response = await this.digestAuth.request({
|
||||
const response = await this.request({
|
||||
url: url.toString(),
|
||||
httpsAgent: reolinkHttpsAgent,
|
||||
responseType: 'json',
|
||||
});
|
||||
return {
|
||||
value: response.data?.[0]?.value?.rspCode,
|
||||
data: response.data,
|
||||
value: response.body?.[0]?.value?.rspCode,
|
||||
data: response.body,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,7 +109,7 @@ export class ReolinkCameraClient {
|
||||
// }
|
||||
// ]
|
||||
async getMotionState() {
|
||||
return getMotionState(this.digestAuth, this.username, this.password, this.host, this.channelId);
|
||||
return getMotionState(this.credential, this.username, this.password, this.host, this.channelId);
|
||||
}
|
||||
|
||||
async getAiState() {
|
||||
@@ -87,13 +119,13 @@ export class ReolinkCameraClient {
|
||||
params.set('channel', this.channelId.toString());
|
||||
params.set('user', this.username);
|
||||
params.set('password', this.password);
|
||||
const response = await this.digestAuth.request({
|
||||
url: url.toString(),
|
||||
httpsAgent: reolinkHttpsAgent,
|
||||
const response = await this.request({
|
||||
url,
|
||||
responseType: 'json',
|
||||
});
|
||||
return {
|
||||
value: !!response.data?.[0]?.value?.state,
|
||||
data: response.data,
|
||||
value: response.body?.[0]?.value as AIState,
|
||||
data: response.body,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,14 +138,12 @@ export class ReolinkCameraClient {
|
||||
params.set('user', this.username);
|
||||
params.set('password', this.password);
|
||||
|
||||
const response = await this.digestAuth.request({
|
||||
url: url.toString(),
|
||||
responseType: 'arraybuffer',
|
||||
httpsAgent: reolinkHttpsAgent,
|
||||
const response = await this.request({
|
||||
url,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
return Buffer.from(response.data);
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async getEncoderConfiguration(): Promise<Enc> {
|
||||
@@ -124,12 +154,12 @@ export class ReolinkCameraClient {
|
||||
params.set('channel', this.channelId.toString());
|
||||
params.set('user', this.username);
|
||||
params.set('password', this.password);
|
||||
const response = await this.digestAuth.request({
|
||||
url: url.toString(),
|
||||
httpsAgent: reolinkHttpsAgent,
|
||||
const response = await this.request({
|
||||
url,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return response.data?.[0]?.value?.Enc;
|
||||
return response.body?.[0]?.value?.Enc;
|
||||
}
|
||||
|
||||
async getDeviceInfo(): Promise<DevInfo> {
|
||||
@@ -138,11 +168,73 @@ export class ReolinkCameraClient {
|
||||
params.set('cmd', 'GetDevInfo');
|
||||
params.set('user', this.username);
|
||||
params.set('password', this.password);
|
||||
const response = await this.digestAuth.request({
|
||||
url: url.toString(),
|
||||
httpsAgent: reolinkHttpsAgent,
|
||||
const response = await this.request({
|
||||
url,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return response.data?.[0]?.value?.DevInfo;
|
||||
return response.body?.[0]?.value?.DevInfo;
|
||||
}
|
||||
|
||||
async ptz(command: PanTiltZoomCommand) {
|
||||
let op = '';
|
||||
if (command.pan < 0)
|
||||
op += 'Left';
|
||||
else if (command.pan > 0)
|
||||
op += 'Right'
|
||||
if (command.tilt < 0)
|
||||
op += 'Down';
|
||||
else if (command.tilt > 0)
|
||||
op += 'Up';
|
||||
|
||||
if (!op)
|
||||
return;
|
||||
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'PtzCtrl');
|
||||
params.set('user', this.username);
|
||||
params.set('password', this.password);
|
||||
|
||||
const createReadable = (data: any) => {
|
||||
const pt = new PassThrough();
|
||||
pt.write(Buffer.from(JSON.stringify(data)));
|
||||
pt.end();
|
||||
return pt;
|
||||
}
|
||||
|
||||
const c1 = this.request({
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'text',
|
||||
}, createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
channel: this.channelId,
|
||||
op,
|
||||
speed: 10,
|
||||
timeout: 1,
|
||||
}
|
||||
},
|
||||
]));
|
||||
|
||||
await sleep(500);
|
||||
|
||||
const c2 = this.request({
|
||||
url,
|
||||
method: 'POST',
|
||||
}, createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
channel: this.channelId,
|
||||
op: "Stop"
|
||||
}
|
||||
},
|
||||
]));
|
||||
|
||||
this.console.log(await c1);
|
||||
this.console.log(await c2);
|
||||
}
|
||||
}
|
||||
|
||||
12875
plugins/rtsp/package-lock.json
generated
12875
plugins/rtsp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -35,14 +35,11 @@
|
||||
"@scrypted/snapshot"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"axios": "^0.23.0",
|
||||
"url-parse": "^1.4.7"
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import sdk, { Setting, MediaObject, ScryptedInterface, FFmpegInput, PictureOptions, SettingValue, MediaStreamOptions, ResponseMediaStreamOptions, ScryptedMimeTypes, MediaStreamUrl } from "@scrypted/sdk";
|
||||
import { EventEmitter } from "stream";
|
||||
import { CameraProviderBase, CameraBase, UrlMediaStreamOptions } from "../../ffmpeg-camera/src/common";
|
||||
import { timeoutPromise } from '@scrypted/common/src/promise-utils';
|
||||
import sdk, { MediaObject, MediaStreamUrl, PictureOptions, ResponseMediaStreamOptions, ScryptedInterface, ScryptedMimeTypes, Setting, SettingValue } from "@scrypted/sdk";
|
||||
import url from 'url';
|
||||
import { timeoutFunction, timeoutPromise } from '@scrypted/common/src/promise-utils';
|
||||
import { CameraBase, CameraProviderBase, UrlMediaStreamOptions } from "../../ffmpeg-camera/src/common";
|
||||
|
||||
export { UrlMediaStreamOptions } from "../../ffmpeg-camera/src/common";
|
||||
|
||||
|
||||
1629
plugins/snapshot/package-lock.json
generated
1629
plugins/snapshot/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/snapshot",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.31",
|
||||
"description": "Snapshot Plugin for Scrypted",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
@@ -34,15 +34,13 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@types/node": "^18.16.18",
|
||||
"axios": "^1.4.0",
|
||||
"sharp": "^0.32.6",
|
||||
"whatwg-mimetype": "^3.0.0"
|
||||
"@types/node": "^20.10.6",
|
||||
"sharp": "^0.33.1",
|
||||
"whatwg-mimetype": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/whatwg-mimetype": "^3.0.0"
|
||||
"@types/whatwg-mimetype": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ export function loadSharp() {
|
||||
hasLoadedSharp = true;
|
||||
try {
|
||||
sharpInstance = require('sharp');
|
||||
// not exposed by sharp but it exists.
|
||||
(sharpInstance.kernel as any).linear = 'linear';
|
||||
console.log('sharp loaded');
|
||||
}
|
||||
catch (e) {
|
||||
@@ -58,8 +60,27 @@ export class VipsImage implements Image {
|
||||
});
|
||||
}
|
||||
if (options?.resize) {
|
||||
let kernel: string;
|
||||
switch (options?.resize.filter) {
|
||||
case 'bilinear':
|
||||
kernel = 'linear';
|
||||
break;
|
||||
case 'lanczos':
|
||||
kernel = 'lanczos2';
|
||||
break;
|
||||
case 'mitchell':
|
||||
kernel = 'mitchell';
|
||||
break;
|
||||
case 'nearest':
|
||||
kernel = 'nearest';
|
||||
break;
|
||||
default:
|
||||
kernel = 'linear';
|
||||
break
|
||||
}
|
||||
transformed.resize(typeof options.resize.width === 'number' ? Math.floor(options.resize.width) : undefined, typeof options.resize.height === 'number' ? Math.floor(options.resize.height) : undefined, {
|
||||
fit: "cover",
|
||||
kernel: kernel as any,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import { AutoenableMixinProvider } from "@scrypted/common/src/autoenable-mixin-provider";
|
||||
import { createMapPromiseDebouncer, RefreshPromise, singletonPromise, TimeoutError } from "@scrypted/common/src/promise-utils";
|
||||
import { AuthFetchCredentialState, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { RefreshPromise, TimeoutError, createMapPromiseDebouncer, singletonPromise } from "@scrypted/common/src/promise-utils";
|
||||
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
||||
import sdk, { BufferConverter, Camera, DeviceManifest, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, MediaObject, MediaObjectOptions, MixinProvider, RequestMediaStreamOptions, RequestPictureOptions, ResponsePictureOptions, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera } from "@scrypted/sdk";
|
||||
import sdk, { BufferConverter, Camera, DeviceManifest, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, MediaObject, MediaObjectOptions, MixinProvider, RequestMediaStreamOptions, RequestPictureOptions, ResponsePictureOptions, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, SettingValue, Settings, VideoCamera } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import https from 'https';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
import { BufferParser } from "../../../server/src/http-fetch-helpers";
|
||||
import { ffmpegFilterImage, ffmpegFilterImageBuffer } from './ffmpeg-image-filter';
|
||||
import { ImageConverter, ImageConverterNativeId } from './image-converter';
|
||||
import { ImageReader, ImageReaderNativeId, loadSharp, loadVipsImage } from './image-reader';
|
||||
import { ImageWriter, ImageWriterNativeId } from './image-writer';
|
||||
|
||||
const { mediaManager, systemManager } = sdk;
|
||||
if (os.cpus().find(cpu => cpu.model?.toLowerCase().includes('qemu'))) {
|
||||
sdk.log.a('QEMU CPU detected. Set your CPU model to host.');
|
||||
}
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
@@ -213,30 +217,27 @@ class SnapshotMixin extends SettingsMixinDeviceBase<Camera> implements Camera {
|
||||
password = settings?.find(setting => setting.key === 'password')?.value?.toString();
|
||||
}
|
||||
|
||||
let axiosClient: AxiosDigestAuth | AxiosInstance;
|
||||
let credential: AuthFetchCredentialState;
|
||||
if (username && password) {
|
||||
axiosClient = new AxiosDigestAuth({
|
||||
username,
|
||||
credential = {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
}
|
||||
else {
|
||||
axiosClient = axios;
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axiosClient.request({
|
||||
const response = await authHttpFetch({
|
||||
httpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
url: this.storageSettings.values.snapshotUrl,
|
||||
credential,
|
||||
}, {
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
'Accept': 'image/*',
|
||||
}
|
||||
});
|
||||
}, BufferParser);
|
||||
|
||||
return response.data;
|
||||
return response.body;
|
||||
}
|
||||
catch (e) {
|
||||
return retryWithPrebuffer(e);
|
||||
@@ -300,7 +301,7 @@ class SnapshotMixin extends SettingsMixinDeviceBase<Camera> implements Camera {
|
||||
picture = this.currentPicture;
|
||||
}
|
||||
|
||||
const needSoftwareResize = !!(options?.picture?.width || options?.picture?.height);
|
||||
const needSoftwareResize = !!(options?.picture?.width || options?.picture?.height) && this.storageSettings.values.snapshotResolution !== 'Full Resolution';
|
||||
if (needSoftwareResize) {
|
||||
try {
|
||||
picture = await this.snapshotDebouncer({
|
||||
@@ -669,6 +670,9 @@ export class SnapshotPlugin extends AutoenableMixinProvider implements MixinProv
|
||||
}
|
||||
};
|
||||
|
||||
if (mixin.storageSettings.values.snapshotResolution === 'Full Resolution')
|
||||
delete rpo.picture;
|
||||
|
||||
if (mixin && iface === ScryptedInterface.Camera) {
|
||||
buffer = await mixin.takePictureRaw(rpo)
|
||||
}
|
||||
|
||||
151
plugins/tapo/package-lock.json
generated
151
plugins/tapo/package-lock.json
generated
@@ -8,42 +8,39 @@
|
||||
"name": "@scrypted/tapo",
|
||||
"version": "0.0.11",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"crc-32": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@koush/axios-digest-auth": "0.8.6",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^18.14.6",
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.2",
|
||||
"dev": true,
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -110,25 +107,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.6.tgz",
|
||||
"integrity": "sha512-e/XKs7/BYpPQkces0Cm4dUmhT9hR0rjvnNZAVRyRnNWdQ8cyCMFWS9HIrMWOdzAocKDNBXi1vKjJ8CywrW5xgQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth/node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
@@ -162,10 +140,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz",
|
||||
"integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==",
|
||||
"dev": true
|
||||
"version": "20.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
|
||||
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
@@ -194,38 +175,6 @@
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
@@ -243,14 +192,6 @@
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@@ -260,68 +201,12 @@
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
@@ -379,6 +264,12 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
|
||||
@@ -30,13 +30,12 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"crc-32": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^18.14.6",
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Deferred } from '@scrypted/common/src/deferred';
|
||||
import { authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { readLine } from '@scrypted/common/src/read-stream';
|
||||
import { parseHeaders, readBody, readMessage, writeMessage } from '@scrypted/common/src/rtsp-server';
|
||||
import axios from 'axios';
|
||||
import crypto from 'crypto';
|
||||
import { Duplex, PassThrough, Writable } from 'stream';
|
||||
import { BufferParser, StreamParser } from '../../../server/src/http-fetch-helpers';
|
||||
import { digestAuthHeader } from './digest-auth';
|
||||
|
||||
export function getTapoAdminPassword(cloudPassword: string, useSHA256: boolean) {
|
||||
@@ -27,16 +28,19 @@ export class TapoAPI {
|
||||
const url = `http://${options.address}/stream`;
|
||||
|
||||
// will fail with auth required.
|
||||
const response = await axios({
|
||||
const response = await authHttpFetch({
|
||||
credential: undefined,
|
||||
url: url,
|
||||
ignoreStatusCode: true,
|
||||
}, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'multipart/mixed; boundary=--client-stream-boundary--',
|
||||
},
|
||||
validateStatus(status) {
|
||||
return status === 401;
|
||||
},
|
||||
});
|
||||
}, BufferParser);
|
||||
|
||||
if (response.statusCode !== 401)
|
||||
throw new Error('Expected 401 status code for two way audio init')
|
||||
|
||||
const wwwAuthenticate = response.headers['www-authenticate'];
|
||||
const useSHA256 = wwwAuthenticate.includes('encrypt_type="3"');
|
||||
@@ -45,20 +49,21 @@ export class TapoAPI {
|
||||
|
||||
const auth = digestAuthHeader('POST', '/stream', wwwAuthenticate, 'admin', password, 0) + ', algorithm=MD5';
|
||||
|
||||
const response2 = await axios({
|
||||
const response2 = await authHttpFetch({
|
||||
credential: undefined,
|
||||
url: url,
|
||||
}, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': auth,
|
||||
'Content-Type': 'multipart/mixed; boundary=--client-stream-boundary--',
|
||||
},
|
||||
responseType: "stream",
|
||||
})
|
||||
}, StreamParser)
|
||||
|
||||
const tapo = new TapoAPI();
|
||||
tapo.keyExchange = response2.headers['key-exchange'];
|
||||
tapo.stream = response2.data.socket;
|
||||
tapo.stream.on('close', () => console.error('strema closed'));
|
||||
tapo.keyExchange = response2.headers['key-exchange'] as string;
|
||||
tapo.stream = response2.body.socket;
|
||||
tapo.stream.on('close', () => console.error('stream closed'));
|
||||
// this.stream.on('data', data => console.log('data', data));
|
||||
// this.stream.resume();
|
||||
return tapo;
|
||||
|
||||
4
plugins/tensorflow-lite/package-lock.json
generated
4
plugins/tensorflow-lite/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.44",
|
||||
"version": "0.1.45",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.44",
|
||||
"version": "0.1.45",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
"scrypted": {
|
||||
"name": "Tensorflow Lite Object Detection",
|
||||
"pluginDependencies": [
|
||||
"@scrypted/objectdetector",
|
||||
"@scrypted/python-codecs"
|
||||
"@scrypted/objectdetector"
|
||||
],
|
||||
"runtime": "python",
|
||||
"pythonVersion": {
|
||||
@@ -54,5 +53,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.44"
|
||||
"version": "0.1.45"
|
||||
}
|
||||
|
||||
20
plugins/webrtc/package-lock.json
generated
20
plugins/webrtc/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
@@ -26,10 +26,11 @@
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../external/werift/packages/webrtc": {
|
||||
@@ -83,12 +84,12 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.68",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -153,10 +154,11 @@
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^16.9.0",
|
||||
"@types/node": "^20.10.8",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
@@ -166,7 +168,7 @@
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user