mirror of
https://github.com/koush/scrypted.git
synced 2026-02-06 23:42:19 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe165295fb |
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -13,11 +13,11 @@ Before opening an issue, view the device's Console logs in the Scrypted Manageme
|
||||
|
||||
**DO NOT OPEN ISSUES FOR ANY OF THE FOLLOWING:**
|
||||
|
||||
* Server or hardware setup assistance. Use Discord, Reddit, or Github Discussions.
|
||||
* Server setup assistance. Use Discord, Reddit, or Github Discussions.
|
||||
* Hardware setup assistance. Use Discord, Reddit, or Github Discussions.
|
||||
* Feature Requests. Use Discord, Reddit, or Github Discussions.
|
||||
* Packet loss in your camera logs. This is wifi/network congestion.
|
||||
* HomeKit weirdness. See HomeKit troubleshooting guide.
|
||||
* Release schedules or timelines. Releases are rolled out unevenly across the different server platforms.
|
||||
|
||||
However, if something **was working**, and is now **no longer working**, you may create a Github issue.
|
||||
Created issues that do not meet these requirements or are improperly filled out will be immediately closed.
|
||||
|
||||
10
.github/workflows/build-plugins-changed.yml
vendored
10
.github/workflows/build-plugins-changed.yml
vendored
@@ -1,11 +1,11 @@
|
||||
name: Build changed plugins
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches: ["main"]
|
||||
# paths: ["plugins/**"]
|
||||
# pull_request:
|
||||
# paths: ["plugins/**"]
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths: ["plugins/**"]
|
||||
pull_request:
|
||||
paths: ["plugins/**"]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
97
.github/workflows/docker-common.yml
vendored
97
.github/workflows/docker-common.yml
vendored
@@ -7,10 +7,13 @@ jobs:
|
||||
build:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: self-hosted
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
# runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
NODE_VERSION: [
|
||||
# "18",
|
||||
"20"
|
||||
]
|
||||
BASE: ["jammy"]
|
||||
FLAVOR: ["full", "lite"]
|
||||
steps:
|
||||
@@ -20,26 +23,12 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
@@ -65,84 +54,14 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: |
|
||||
NODE_VERSION=${{ env.NODE_VERSION }}
|
||||
NODE_VERSION=${{ matrix.NODE_VERSION }}
|
||||
BASE=${{ matrix.BASE }}
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile.${{ matrix.FLAVOR }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
build-nvidia:
|
||||
name: Push NVIDIA Docker image to Docker Hub
|
||||
needs: build
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
BASE: ["jammy"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
platforms: linux/arm64
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image (scrypted-common)
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: |
|
||||
BASE=ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-full
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile.nvidia
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted-common:${{ matrix.BASE }}-nvidia
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-nvidia
|
||||
koush/scrypted-common:${{ matrix.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
|
||||
|
||||
44
.github/workflows/docker.yml
vendored
44
.github/workflows/docker.yml
vendored
@@ -20,10 +20,10 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
BASE: [
|
||||
["jammy-nvidia", ".s6"],
|
||||
["jammy-full", ".s6"],
|
||||
["jammy-lite", ""],
|
||||
"20-jammy-full",
|
||||
"20-jammy-lite",
|
||||
]
|
||||
SUPERVISOR: ["", ".s6"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
@@ -42,26 +42,12 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
@@ -87,23 +73,23 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: |
|
||||
BASE=${{ matrix.BASE[0] }}
|
||||
BASE=${{ matrix.BASE }}
|
||||
SCRYPTED_INSTALL_VERSION=${{ steps.package-version.outputs.NPM_VERSION }}
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile${{ matrix.BASE[1] }}
|
||||
file: install/docker/Dockerfile${{ matrix.SUPERVISOR }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ format('koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE[0] == 'jammy-full' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-nvidia' && 'koush/scrypted:nvidia' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-full' && 'koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-lite' && 'koush/scrypted:lite' || '' }}
|
||||
${{ format('koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '.s6' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '' && 'koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '' && 'koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '.s6' && 'koush/scrypted:lite-s6' || '' }}
|
||||
|
||||
${{ format('ghcr.io/koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE[0] == 'jammy-full' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-nvidia' && 'ghcr.io/koush/scrypted:nvidia' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-full' && 'ghcr.io/koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-lite' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
${{ format('ghcr.io/koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '.s6' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '.s6' && 'ghcr.io/koush/scrypted:lite-s6' || '' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
64
.github/workflows/test.yml
vendored
64
.github/workflows/test.yml
vendored
@@ -9,28 +9,52 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test_local:
|
||||
name: Test local installation on ${{ matrix.runner }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runner: [ubuntu-latest, macos-14, macos-13, windows-latest]
|
||||
test_linux_local:
|
||||
name: Test Linux local installation
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Parse latest server release
|
||||
id: parse_server
|
||||
shell: bash
|
||||
|
||||
- name: Run install script
|
||||
run: |
|
||||
VERSION=$(cat ./server/package-lock.json | jq -r '.version')
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Will test @scrypted/server@$VERSION"
|
||||
|
||||
- name: Install scrypted server
|
||||
uses: scryptedapp/setup-scrypted@v0.0.2
|
||||
with:
|
||||
branch: ${{ github.sha }}
|
||||
version: ${{ steps.parse_server.outputs.version }}
|
||||
cat ./install/local/install-scrypted-dependencies-linux.sh | sudo SERVICE_USER=$USER bash
|
||||
|
||||
- name: Test server is running
|
||||
run: |
|
||||
systemctl status scrypted.service
|
||||
curl -k --retry 20 --retry-all-errors --retry-max-time 600 https://localhost:10443/
|
||||
|
||||
test_mac_local:
|
||||
name: Test Mac local installation
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run install script
|
||||
run: |
|
||||
mkdir -p ~/.scrypted
|
||||
bash ./install/local/install-scrypted-dependencies-mac.sh
|
||||
|
||||
- name: Test server is running
|
||||
run: |
|
||||
curl -k --retry 20 --retry-all-errors --retry-max-time 600 https://localhost:10443/
|
||||
|
||||
test_windows_local:
|
||||
name: Test Windows local installation
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run install script
|
||||
run: |
|
||||
.\install\local\install-scrypted-dependencies-win.ps1
|
||||
|
||||
- name: Test server is running
|
||||
run: |
|
||||
curl -k --retry 20 --retry-all-errors --retry-max-time 600 https://localhost:10443/
|
||||
|
||||
1
common/fs/@types/sdk/settings-mixin.d.ts
vendored
1
common/fs/@types/sdk/settings-mixin.d.ts
vendored
@@ -1 +0,0 @@
|
||||
../../../../sdk/dist/src/settings-mixin.d.ts
|
||||
1
common/fs/@types/sdk/storage-settings.d.ts
vendored
1
common/fs/@types/sdk/storage-settings.d.ts
vendored
@@ -1 +0,0 @@
|
||||
../../../../sdk/dist/src/storage-settings.d.ts
|
||||
103
common/package-lock.json
generated
103
common/package-lock.json
generated
@@ -74,7 +74,7 @@
|
||||
},
|
||||
"../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.29",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -111,57 +111,64 @@
|
||||
},
|
||||
"../server": {
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.106.0",
|
||||
"hasInstallScript": true,
|
||||
"version": "0.82.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"@scrypted/ffmpeg-static": "^6.1.0-build1",
|
||||
"@scrypted/node-pty": "^1.0.10",
|
||||
"@scrypted/types": "^0.3.28",
|
||||
"adm-zip": "^0.5.12",
|
||||
"@scrypted/types": "^0.3.4",
|
||||
"adm-zip": "^0.5.10",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.4.5",
|
||||
"debug": "^4.3.4",
|
||||
"engine.io": "^6.5.4",
|
||||
"express": "^4.19.2",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"express": "^4.18.2",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"follow-redirects": "^1.15.4",
|
||||
"http-auth": "^4.2.0",
|
||||
"ip": "^2.0.1",
|
||||
"level": "^8.0.1",
|
||||
"ip": "^1.1.8",
|
||||
"level": "^8.0.0",
|
||||
"linkfs": "^2.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nan": "^2.19.0",
|
||||
"memfs": "^4.6.0",
|
||||
"mime": "^3.0.0",
|
||||
"nan": "^2.18.0",
|
||||
"node-dijkstra": "^2.5.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-gyp": "^10.1.0",
|
||||
"py": "npm:@bjia56/portable-python@^0.1.31",
|
||||
"node-gyp": "^10.0.1",
|
||||
"router": "^1.3.8",
|
||||
"semver": "^7.6.2",
|
||||
"sharp": "^0.33.3",
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "^0.33.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tar": "^7.1.0",
|
||||
"tar": "^6.2.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript": "^5.3.3",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"ws": "^8.17.0"
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-serve": "bin/scrypted-serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@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.17.1",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node-dijkstra": "^2.5.6",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@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": {
|
||||
@@ -446,47 +453,53 @@
|
||||
"version": "file:../server",
|
||||
"requires": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"@scrypted/ffmpeg-static": "^6.1.0-build1",
|
||||
"@scrypted/node-pty": "^1.0.10",
|
||||
"@scrypted/types": "^0.3.28",
|
||||
"@scrypted/types": "^0.3.4",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@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.17.1",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node-dijkstra": "^2.5.6",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@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.12",
|
||||
"adm-zip": "^0.5.10",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.4.5",
|
||||
"debug": "^4.3.4",
|
||||
"engine.io": "^6.5.4",
|
||||
"express": "^4.19.2",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"express": "^4.18.2",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"follow-redirects": "^1.15.4",
|
||||
"http-auth": "^4.2.0",
|
||||
"ip": "^2.0.1",
|
||||
"level": "^8.0.1",
|
||||
"ip": "^1.1.8",
|
||||
"level": "^8.0.0",
|
||||
"linkfs": "^2.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nan": "^2.19.0",
|
||||
"memfs": "^4.6.0",
|
||||
"mime": "^3.0.0",
|
||||
"nan": "^2.18.0",
|
||||
"node-dijkstra": "^2.5.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-gyp": "^10.1.0",
|
||||
"py": "npm:@bjia56/portable-python@^0.1.31",
|
||||
"node-gyp": "^10.0.1",
|
||||
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5",
|
||||
"router": "^1.3.8",
|
||||
"semver": "^7.6.2",
|
||||
"sharp": "^0.33.3",
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "^0.33.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tar": "^7.1.0",
|
||||
"tar": "^6.2.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript": "^5.3.3",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"ws": "^8.17.0"
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
export function createActivityTimeout(timeout: number, timeoutCallback: () => void) {
|
||||
let dataTimeout: NodeJS.Timeout;
|
||||
|
||||
let lastTime = Date.now();
|
||||
function resetActivityTimer() {
|
||||
lastTime = Date.now();
|
||||
}
|
||||
|
||||
function clearActivityTimer() {
|
||||
clearInterval(dataTimeout);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
dataTimeout = setInterval(() => {
|
||||
if (Date.now() > lastTime + timeout) {
|
||||
clearInterval(dataTimeout);
|
||||
dataTimeout = undefined;
|
||||
timeoutCallback();
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
resetActivityTimer();
|
||||
return {
|
||||
resetActivityTimer,
|
||||
clearActivityTimer,
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import sdk, { MixinDeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import { SettingsMixinDeviceBase } from "@scrypted/sdk/settings-mixin";
|
||||
import sdk, { MixinDeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors } from "@scrypted/sdk";
|
||||
import fs from 'fs';
|
||||
import type { TranspileOptions } from "typescript";
|
||||
import vm from "vm";
|
||||
import { ScriptDevice } from "./monaco/script-device";
|
||||
import path from 'path';
|
||||
|
||||
const { systemManager, deviceManager, mediaManager, endpointManager } = sdk;
|
||||
|
||||
@@ -29,13 +28,9 @@ export function readFileAsString(f: string) {
|
||||
}
|
||||
|
||||
function getTypeDefs() {
|
||||
const settingsMixinDefs = readFileAsString('@types/sdk/settings-mixin.d.ts');
|
||||
const storageSettingsDefs = readFileAsString('@types/sdk/storage-settings.d.ts');
|
||||
const scryptedTypesDefs = readFileAsString('@types/sdk/types.d.ts');
|
||||
const scryptedIndexDefs = readFileAsString('@types/sdk/index.d.ts');
|
||||
return {
|
||||
settingsMixinDefs,
|
||||
storageSettingsDefs,
|
||||
scryptedIndexDefs,
|
||||
scryptedTypesDefs,
|
||||
};
|
||||
@@ -69,7 +64,6 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
fs: require('realfs'),
|
||||
ScryptedDeviceBase,
|
||||
MixinDeviceBase,
|
||||
StorageSettings,
|
||||
systemManager,
|
||||
deviceManager,
|
||||
endpointManager,
|
||||
@@ -79,8 +73,6 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
localStorage: device.storage,
|
||||
device,
|
||||
exports: {} as any,
|
||||
SettingsMixinDeviceBase,
|
||||
ScryptedMimeTypes,
|
||||
ScryptedInterface,
|
||||
ScryptedDeviceType,
|
||||
// @ts-expect-error
|
||||
@@ -181,16 +173,6 @@ export function createMonacoEvalDefaults(extraLibs: { [lib: string]: string }) {
|
||||
"node_modules/@types/scrypted__sdk/types/index.d.ts"
|
||||
);
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
libs['settingsMixin'],
|
||||
"node_modules/@types/scrypted__sdk/settings-mixin.d.ts"
|
||||
);
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
libs['storageSettings'],
|
||||
"node_modules/@types/scrypted__sdk/storage-settings.d.ts"
|
||||
);
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
libs['sdk'],
|
||||
"node_modules/@types/scrypted__sdk/index.d.ts"
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { cloneDeep } from '@scrypted/common/src/clone-deep';
|
||||
import { Deferred } from "@scrypted/common/src/deferred";
|
||||
import { listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
||||
import { ffmpegLogInitialOutput, safeKillFFmpeg, safePrintFFmpegArguments } from '@scrypted/common/src/media-helpers';
|
||||
import { createActivityTimeout } from '@scrypted/common/src/activity-timeout';
|
||||
import { createRtspParser } from "@scrypted/common/src/rtsp-server";
|
||||
import { parseSdp } from "@scrypted/common/src/sdp-utils";
|
||||
import { StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
|
||||
import sdk, { FFmpegInput, RequestMediaStreamOptions, ResponseMediaStreamOptions } from "@scrypted/sdk";
|
||||
import child_process, { ChildProcess, StdioOptions } from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Server } from 'net';
|
||||
import { Duplex } from 'stream';
|
||||
import { cloneDeep } from './clone-deep';
|
||||
import { Deferred } from "./deferred";
|
||||
import { listenZeroSingleClient } from './listen-cluster';
|
||||
import { ffmpegLogInitialOutput, safeKillFFmpeg, safePrintFFmpegArguments } from './media-helpers';
|
||||
import { createRtspParser } from "./rtsp-server";
|
||||
import { parseSdp } from "./sdp-utils";
|
||||
import { StreamChunk, StreamParser } from './stream-parser';
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
export interface ParserSession<T extends string> {
|
||||
parserSpecific?: any;
|
||||
sdp: Promise<string>;
|
||||
sdp: Promise<Buffer[]>;
|
||||
resetActivityTimer?: () => void,
|
||||
negotiateMediaStream(requestMediaStream: RequestMediaStreamOptions, inputVideoCodec: string, inputAudioCodec: string): ResponseMediaStreamOptions;
|
||||
negotiateMediaStream(requestMediaStream: RequestMediaStreamOptions): ResponseMediaStreamOptions;
|
||||
inputAudioCodec?: string;
|
||||
inputVideoCodec?: string;
|
||||
inputVideoResolution?: {
|
||||
width: number,
|
||||
height: number,
|
||||
},
|
||||
start(): void;
|
||||
kill(error?: Error): void;
|
||||
killed: Promise<void>;
|
||||
@@ -24,7 +31,6 @@ export interface ParserSession<T extends string> {
|
||||
|
||||
emit(container: T, chunk: StreamChunk): this;
|
||||
on(container: T, callback: (chunk: StreamChunk) => void): this;
|
||||
on(error: 'error', callback: (e: Error) => void): this;
|
||||
removeListener(event: T | 'killed', callback: any): this;
|
||||
once(event: T | 'killed', listener: (...args: any[]) => void): this;
|
||||
}
|
||||
@@ -96,37 +102,65 @@ export async function parseAudioCodec(cp: ChildProcess) {
|
||||
export function setupActivityTimer(container: string, kill: (error?: Error) => void, events: {
|
||||
once(event: 'killed', callback: () => void): void,
|
||||
}, timeout: number) {
|
||||
const ret = createActivityTimeout(timeout, () => {
|
||||
let dataTimeout: NodeJS.Timeout;
|
||||
|
||||
function dataKill() {
|
||||
const str = 'timeout waiting for data, killing parser session';
|
||||
console.error(str, container);
|
||||
kill(new Error(str));
|
||||
});
|
||||
events.once('killed', () => ret.clearActivityTimer());
|
||||
return ret;
|
||||
}
|
||||
|
||||
let lastTime = Date.now();
|
||||
function resetActivityTimer() {
|
||||
lastTime = Date.now();
|
||||
}
|
||||
|
||||
function clearActivityTimer() {
|
||||
clearInterval(dataTimeout);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
dataTimeout = setInterval(() => {
|
||||
if (Date.now() > lastTime + timeout) {
|
||||
clearInterval(dataTimeout);
|
||||
dataTimeout = undefined;
|
||||
dataKill();
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
events.once('killed', () => clearInterval(dataTimeout));
|
||||
|
||||
resetActivityTimer();
|
||||
return {
|
||||
resetActivityTimer,
|
||||
clearActivityTimer,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function startParserSession<T extends string>(ffmpegInput: FFmpegInput, options: ParserOptions<T>): Promise<ParserSession<T>> {
|
||||
const { console } = options;
|
||||
|
||||
let isActive = true;
|
||||
const events = new EventEmitter();
|
||||
// need this to prevent kill from throwing due to uncaught Error during cleanup
|
||||
events.on('error', () => {});
|
||||
events.on('error', e => console.error('rebroadcast error', e));
|
||||
|
||||
let inputAudioCodec: string;
|
||||
let inputVideoCodec: string;
|
||||
let inputVideoResolution: string[];
|
||||
|
||||
let sessionKilled: any;
|
||||
const killed = new Promise<void>(resolve => {
|
||||
sessionKilled = resolve;
|
||||
});
|
||||
|
||||
const sdpDeferred = new Deferred<string>();
|
||||
function kill(error?: Error) {
|
||||
error ||= new Error('killed');
|
||||
if (isActive) {
|
||||
events.emit('killed');
|
||||
events.emit('error', error);
|
||||
events.emit('error', error || new Error('killed'));
|
||||
}
|
||||
if (!sdpDeferred.finished)
|
||||
sdpDeferred.reject(error);
|
||||
isActive = false;
|
||||
sessionKilled();
|
||||
safeKillFFmpeg(cp);
|
||||
@@ -166,7 +200,7 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
try {
|
||||
ensureActive(() => socket.destroy());
|
||||
|
||||
for await (const chunk of parser.parse(socket, undefined, undefined)) {
|
||||
for await (const chunk of parser.parse(socket, parseInt(inputVideoResolution?.[2]), parseInt(inputVideoResolution?.[3]))) {
|
||||
events.emit(container, chunk);
|
||||
resetActivityTimer();
|
||||
}
|
||||
@@ -213,7 +247,7 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
try {
|
||||
const { resetActivityTimer } = setupActivityTimer(container, kill, events, options?.timeout);
|
||||
|
||||
for await (const chunk of parser.parse(pipe as any, undefined, undefined)) {
|
||||
for await (const chunk of parser.parse(pipe as any, parseInt(inputVideoResolution?.[2]), parseInt(inputVideoResolution?.[3]))) {
|
||||
await deferredStart.promise;
|
||||
events.emit(container, chunk);
|
||||
resetActivityTimer();
|
||||
@@ -227,7 +261,17 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
};
|
||||
|
||||
const rtsp = (options.parsers as any).rtsp as ReturnType<typeof createRtspParser>;
|
||||
rtsp.sdp.then(sdp => sdpDeferred.resolve(sdp));
|
||||
rtsp.sdp.then(sdp => {
|
||||
const parsed = parseSdp(sdp);
|
||||
const audio = parsed.msections.find(msection => msection.type === 'audio');
|
||||
const video = parsed.msections.find(msection => msection.type === 'video');
|
||||
inputVideoCodec = video?.codec;
|
||||
inputAudioCodec = audio?.codec;
|
||||
});
|
||||
|
||||
const sdp = new Deferred<Buffer[]>();
|
||||
rtsp.sdp.then(r => sdp.resolve([Buffer.from(r)]));
|
||||
killed.then(() => sdp.reject(new Error("ffmpeg killed before sdp could be parsed")));
|
||||
|
||||
start();
|
||||
|
||||
@@ -235,13 +279,25 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
start() {
|
||||
deferredStart.resolve();
|
||||
},
|
||||
sdp: sdpDeferred.promise,
|
||||
sdp: sdp.promise,
|
||||
get inputAudioCodec() {
|
||||
return inputAudioCodec;
|
||||
},
|
||||
get inputVideoCodec() {
|
||||
return inputVideoCodec;
|
||||
},
|
||||
get inputVideoResolution() {
|
||||
return {
|
||||
width: parseInt(inputVideoResolution?.[2]),
|
||||
height: parseInt(inputVideoResolution?.[3]),
|
||||
}
|
||||
},
|
||||
get isActive() { return isActive },
|
||||
kill(error?: Error) {
|
||||
kill(error);
|
||||
},
|
||||
killed,
|
||||
negotiateMediaStream: (requestMediaStream: RequestMediaStreamOptions, inputVideoCodec, inputAudioCodec) => {
|
||||
negotiateMediaStream: () => {
|
||||
const ret: ResponseMediaStreamOptions = cloneDeep(ffmpegInput.mediaStreamOptions) || {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
@@ -283,3 +339,64 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface Rebroadcaster {
|
||||
server: Server;
|
||||
port: number;
|
||||
url: string;
|
||||
clients: number;
|
||||
}
|
||||
|
||||
export interface RebroadcastSessionCleanup {
|
||||
(): void;
|
||||
}
|
||||
|
||||
export interface RebroadcasterConnection {
|
||||
writeData: (data: StreamChunk) => number;
|
||||
destroy: () => void;
|
||||
}
|
||||
|
||||
export interface RebroadcasterOptions {
|
||||
connect?: (connection: RebroadcasterConnection) => RebroadcastSessionCleanup | undefined;
|
||||
console?: Console;
|
||||
idle?: {
|
||||
timeout: number,
|
||||
callback: () => void,
|
||||
},
|
||||
}
|
||||
|
||||
export function handleRebroadcasterClient(socket: Duplex, options?: RebroadcasterOptions) {
|
||||
const firstWriteData = (data: StreamChunk) => {
|
||||
if (data.startStream) {
|
||||
socket.write(data.startStream)
|
||||
}
|
||||
connection.writeData = writeData;
|
||||
return writeData(data);
|
||||
}
|
||||
const writeData = (data: StreamChunk) => {
|
||||
for (const chunk of data.chunks) {
|
||||
socket.write(chunk);
|
||||
}
|
||||
|
||||
return socket.writableLength;
|
||||
};
|
||||
|
||||
const destroy = () => {
|
||||
const cb = cleanupCallback;
|
||||
cleanupCallback = undefined;
|
||||
socket.destroy();
|
||||
cb?.();
|
||||
}
|
||||
|
||||
const connection: RebroadcasterConnection = {
|
||||
writeData: firstWriteData,
|
||||
destroy,
|
||||
};
|
||||
|
||||
let cleanupCallback = options?.connect(connection);
|
||||
|
||||
socket.once('close', () => {
|
||||
destroy();
|
||||
});
|
||||
socket.on('error', e => options?.console?.log('client stream ended'));
|
||||
}
|
||||
@@ -136,17 +136,12 @@ export async function readLine(readable: Readable) {
|
||||
}
|
||||
|
||||
export async function readString(readable: Readable | Promise<Readable>) {
|
||||
const buffer = await readBuffer(readable);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
export async function readBuffer(readable: Readable | Promise<Readable>) {
|
||||
const buffers: Buffer[] = [];
|
||||
let data = '';
|
||||
readable = await readable;
|
||||
readable.on('data', buffer => {
|
||||
buffers.push(buffer);
|
||||
data += buffer.toString();
|
||||
});
|
||||
readable.resume();
|
||||
await once(readable, 'end')
|
||||
return Buffer.concat(buffers);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -89,44 +89,27 @@ export const H264_NAL_TYPE_FU_B = 29;
|
||||
export const H264_NAL_TYPE_MTAP16 = 26;
|
||||
export const H264_NAL_TYPE_MTAP32 = 27;
|
||||
|
||||
export const H265_NAL_TYPE_AGG = 48;
|
||||
export const H265_NAL_TYPE_VPS = 32;
|
||||
export const H265_NAL_TYPE_SPS = 33;
|
||||
export const H265_NAL_TYPE_PPS = 34;
|
||||
export const H265_NAL_TYPE_IDR_N = 19;
|
||||
export const H265_NAL_TYPE_IDR_W = 20;
|
||||
|
||||
export function findH264NaluType(streamChunk: StreamChunk, naluType: number) {
|
||||
if (streamChunk.type !== 'h264')
|
||||
return;
|
||||
return findH264NaluTypeInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), naluType);
|
||||
}
|
||||
|
||||
export function findH265NaluType(streamChunk: StreamChunk, naluType: number) {
|
||||
if (streamChunk.type !== 'h265')
|
||||
return;
|
||||
return findH265NaluTypeInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), naluType);
|
||||
}
|
||||
|
||||
export function parseH264NaluType(firstNaluByte: number) {
|
||||
return firstNaluByte & 0x1f;
|
||||
}
|
||||
|
||||
export function findH264NaluTypeInNalu(nalu: Buffer, naluType: number) {
|
||||
const checkNaluType = parseH264NaluType(nalu[0]);
|
||||
const checkNaluType = nalu[0] & 0x1f;
|
||||
if (checkNaluType === H264_NAL_TYPE_STAP_A) {
|
||||
let pos = 1;
|
||||
while (pos < nalu.length) {
|
||||
const naluLength = nalu.readUInt16BE(pos);
|
||||
pos += 2;
|
||||
const stapaType = parseH264NaluType(nalu[pos]);
|
||||
const stapaType = nalu[pos] & 0x1f;
|
||||
if (stapaType === naluType)
|
||||
return nalu.subarray(pos, pos + naluLength);
|
||||
pos += naluLength;
|
||||
}
|
||||
}
|
||||
else if (checkNaluType === H264_NAL_TYPE_FU_A) {
|
||||
const fuaType = parseH264NaluType(nalu[1]);
|
||||
const fuaType = nalu[1] & 0x1f;
|
||||
const isFuStart = !!(nalu[1] & 0x80);
|
||||
|
||||
if (fuaType === naluType && isFuStart)
|
||||
@@ -138,52 +121,39 @@ export function findH264NaluTypeInNalu(nalu: Buffer, naluType: number) {
|
||||
return;
|
||||
}
|
||||
|
||||
function parseH265NaluType(firstNaluByte: number) {
|
||||
return (firstNaluByte & 0b01111110) >> 1;
|
||||
}
|
||||
|
||||
export function findH265NaluTypeInNalu(nalu: Buffer, naluType: number) {
|
||||
const checkNaluType = parseH265NaluType(nalu[0]);
|
||||
if (checkNaluType === H265_NAL_TYPE_AGG) {
|
||||
let pos = 1;
|
||||
while (pos < nalu.length) {
|
||||
const naluLength = nalu.readUInt16BE(pos);
|
||||
pos += 2;
|
||||
const stapaType = parseH265NaluType(nalu[pos]);
|
||||
if (stapaType === naluType)
|
||||
return nalu.subarray(pos, pos + naluLength);
|
||||
pos += naluLength;
|
||||
}
|
||||
}
|
||||
else if (checkNaluType === naluType) {
|
||||
return nalu;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export function getNaluTypes(streamChunk: StreamChunk) {
|
||||
if (streamChunk.type !== 'h264')
|
||||
return new Set<number>();
|
||||
return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12))
|
||||
}
|
||||
|
||||
export function getNaluFragmentInformation(nalu: Buffer) {
|
||||
const naluType = nalu[0] & 0x1f;
|
||||
const fua = naluType === H264_NAL_TYPE_FU_A;
|
||||
return {
|
||||
fua,
|
||||
fuaStart: fua && !!(nalu[1] & 0x80),
|
||||
fuaEnd: fua && !!(nalu[1] & 0x40),
|
||||
}
|
||||
}
|
||||
|
||||
export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) {
|
||||
const ret = new Set<number>();
|
||||
const naluType = parseH264NaluType(nalu[0]);
|
||||
const naluType = nalu[0] & 0x1f;
|
||||
if (naluType === H264_NAL_TYPE_STAP_A) {
|
||||
ret.add(H264_NAL_TYPE_STAP_A);
|
||||
let pos = 1;
|
||||
while (pos < nalu.length) {
|
||||
const naluLength = nalu.readUInt16BE(pos);
|
||||
pos += 2;
|
||||
const stapaType = parseH264NaluType(nalu[pos]);
|
||||
const stapaType = nalu[pos] & 0x1f;
|
||||
ret.add(stapaType);
|
||||
pos += naluLength;
|
||||
}
|
||||
}
|
||||
else if (naluType === H264_NAL_TYPE_FU_A) {
|
||||
ret.add(H264_NAL_TYPE_FU_A);
|
||||
const fuaType = parseH264NaluType(nalu[1]);
|
||||
const fuaType = nalu[1] & 0x1f;
|
||||
if (fuaRequireStart) {
|
||||
const isFuStart = !!(nalu[1] & 0x80);
|
||||
if (isFuStart)
|
||||
@@ -205,33 +175,6 @@ export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaReq
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function getH265NaluTypes(streamChunk: StreamChunk) {
|
||||
if (streamChunk.type !== 'h265')
|
||||
return new Set<number>();
|
||||
return getNaluTypesInH265Nalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12))
|
||||
}
|
||||
|
||||
export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) {
|
||||
const ret = new Set<number>();
|
||||
const naluType = parseH265NaluType(nalu[0]);
|
||||
if (naluType === H265_NAL_TYPE_AGG) {
|
||||
ret.add(H265_NAL_TYPE_AGG);
|
||||
let pos = 1;
|
||||
while (pos < nalu.length) {
|
||||
const naluLength = nalu.readUInt16BE(pos);
|
||||
pos += 2;
|
||||
const stapaType = parseH265NaluType(nalu[pos]);
|
||||
ret.add(stapaType);
|
||||
pos += naluLength;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret.add(naluType);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function createRtspParser(options?: StreamParserOptions): RtspStreamParser {
|
||||
let resolve: any;
|
||||
|
||||
@@ -252,23 +195,12 @@ export function createRtspParser(options?: StreamParserOptions): RtspStreamParse
|
||||
findSyncFrame(streamChunks: StreamChunk[]) {
|
||||
for (let prebufferIndex = 0; prebufferIndex < streamChunks.length; prebufferIndex++) {
|
||||
const streamChunk = streamChunks[prebufferIndex];
|
||||
if (streamChunk.type === 'h264') {
|
||||
const naluTypes = getNaluTypes(streamChunk);
|
||||
if (naluTypes.has(H264_NAL_TYPE_SPS) || naluTypes.has(H264_NAL_TYPE_IDR)) {
|
||||
return streamChunks.slice(prebufferIndex);
|
||||
}
|
||||
if (streamChunk.type !== 'h264') {
|
||||
continue;
|
||||
}
|
||||
else if (streamChunk.type === 'h265') {
|
||||
const naluTypes = getH265NaluTypes(streamChunk);
|
||||
|
||||
if (naluTypes.has(H265_NAL_TYPE_VPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_SPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_PPS)
|
||||
|| naluTypes.has(H265_NAL_TYPE_IDR_N)
|
||||
|| naluTypes.has(H265_NAL_TYPE_IDR_W)
|
||||
) {
|
||||
return streamChunks.slice(prebufferIndex);
|
||||
}
|
||||
if (findH264NaluType(streamChunk, H264_NAL_TYPE_SPS) || findH264NaluType(streamChunk, H264_NAL_TYPE_IDR)) {
|
||||
return streamChunks.slice(prebufferIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,7 +540,6 @@ export class RtspClient extends RtspBase {
|
||||
throw new Error('no WWW-Authenticate found');
|
||||
|
||||
const { BASIC } = await import('http-auth-utils');
|
||||
// @ts-ignore
|
||||
const { parseHTTPHeadersQuotedKeyValueSet } = await import('http-auth-utils/dist/utils');
|
||||
|
||||
if (this.wwwAuthenticate.includes('Basic')) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"noImplicitAny": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "v0.111.0-jammy-full"
|
||||
version: "18-jammy-full.s6-v0.93.0"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
arch:
|
||||
- amd64
|
||||
- aarch64
|
||||
- armv7
|
||||
init: false
|
||||
ingress: true
|
||||
ingress_port: 11080
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
# install script.
|
||||
################################################################
|
||||
ARG BASE="jammy"
|
||||
ARG REPO="ubuntu"
|
||||
FROM ${REPO}:${BASE} as header
|
||||
FROM ubuntu:${BASE} as header
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
|
||||
FROM $BASE
|
||||
FROM ghcr.io/koush/scrypted:20-jammy-full.s6
|
||||
|
||||
# nvidia cudnn/libcublas etc.
|
||||
# for some reason this is not provided by the nvidia container toolkit
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-graphics.sh | bash
|
||||
WORKDIR /
|
||||
|
||||
# Install miniconda
|
||||
ENV CONDA_DIR /opt/conda
|
||||
RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \
|
||||
/bin/bash ~/miniconda.sh -b -p /opt/conda
|
||||
# Put conda in path so we can use conda activate
|
||||
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/
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
version: "3.5"
|
||||
|
||||
# The Scrypted docker-compose.yml file typically resides at:
|
||||
# ~/.scrypted/docker-compose.yml
|
||||
|
||||
@@ -35,24 +37,17 @@ services:
|
||||
# Avahi can be used for network discovery by passing in the host daemon
|
||||
# or running the daemon inside the container. Choose one or the other.
|
||||
# Uncomment next line to run avahi-daemon inside the container.
|
||||
# See volumes and security_opt section below to use the host daemon.
|
||||
# See volumes section below to use the host daemon.
|
||||
# - SCRYPTED_DOCKER_AVAHI=true
|
||||
|
||||
# NVIDIA (Part 1 of 4)
|
||||
# Uncomment next 3 lines for Nvidia GPU support.
|
||||
# - NVIDIA_VISIBLE_DEVICES=all
|
||||
# - NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
# NVIDIA (Part 2 of 4)
|
||||
# runtime: nvidia
|
||||
|
||||
# NVIDIA (Part 3 of 4) - Use NVIDIA image, and remove subsequent default image.
|
||||
# image: ghcr.io/koush/scrypted:nvidia
|
||||
image: ghcr.io/koush/scrypted
|
||||
|
||||
# Necessary to communicate with host dbus for avahi-daemon.
|
||||
security_opt:
|
||||
- apparmor:unconfined
|
||||
volumes:
|
||||
# NVIDIA (Part 4 of 4)
|
||||
# - /etc/OpenCL/vendors/nvidia.icd:/etc/OpenCL/vendors/nvidia.icd
|
||||
|
||||
# Scrypted NVR Storage (Part 3 of 3)
|
||||
|
||||
# Modify to add the additional volume for Scrypted NVR.
|
||||
@@ -71,16 +66,11 @@ services:
|
||||
# Ensure Avahi is running on the host machine:
|
||||
# It can be installed with: sudo apt-get install avahi-daemon
|
||||
# This is not compatible with running avahi inside the container (see above).
|
||||
# Also, uncomment the lines under security_opt
|
||||
# - /var/run/dbus:/var/run/dbus
|
||||
# - /var/run/avahi-daemon/socket:/var/run/avahi-daemon/socket
|
||||
|
||||
# Default volume for the Scrypted database. Typically should not be changed.
|
||||
- ~/.scrypted/volume:/server/volume
|
||||
# Uncomment the following lines to use Avahi daemon from the host
|
||||
# Without this, AppArmor will block the container's attempt to talk to Avahi via dbus
|
||||
# security_opt:
|
||||
# - apparmor:unconfined
|
||||
devices: [
|
||||
# uncomment the common systems devices to pass
|
||||
# them through to docker.
|
||||
@@ -104,16 +94,15 @@ services:
|
||||
container_name: scrypted
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
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.
|
||||
# enable the log file if enhanced debugging is necessary.
|
||||
logging:
|
||||
driver: "none"
|
||||
# driver: "json-file"
|
||||
# options:
|
||||
# max-size: "10m"
|
||||
# max-file: "10"
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "10"
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.scope=scrypted"
|
||||
|
||||
|
||||
@@ -1,35 +1,13 @@
|
||||
if [ "$(uname -m)" = "x86_64" ]
|
||||
then
|
||||
# this script previvously apt install intel-media-va-driver-non-free, but that seems to no longer be necessary.
|
||||
|
||||
# the intel provided script is disabled since it does not work with the 6.8 kernel in Ubuntu 24.04 or Proxmox 8.2.
|
||||
# manual installation of the Intel graphics stuff is required.
|
||||
|
||||
# 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 &&
|
||||
# apt-get -y dist-upgrade;
|
||||
|
||||
# manual installation
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
|
||||
rm -rf /tmp/neo && mkdir -p /tmp/neo && cd /tmp/neo &&
|
||||
apt-get install -y ocl-icd-libopencl1 &&
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.16695.4/intel-igc-core_1.0.16695.4_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.16695.4/intel-igc-opencl_1.0.16695.4_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.17.29377.6/intel-level-zero-gpu-dbgsym_1.3.29377.6_amd64.ddeb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.17.29377.6/intel-level-zero-gpu_1.3.29377.6_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.17.29377.6/intel-opencl-icd-dbgsym_24.17.29377.6_amd64.ddeb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.17.29377.6/intel-opencl-icd_24.17.29377.6_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.17.29377.6/libigdgmm12_22.3.19_amd64.deb &&
|
||||
dpkg -i *.deb &&
|
||||
cd /tmp && rm -rf /tmp/neo &&
|
||||
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."
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
if [ "$(uname -m)" = "x86_64" ]
|
||||
then
|
||||
echo "Installing NVIDIA graphics packages."
|
||||
apt update -q \
|
||||
&& apt install -y wget \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& dpkg -i /cuda-keyring.deb \
|
||||
&& apt update -q \
|
||||
&& apt install -y cuda-nvcc-11-8 libcublas-11-8 libcudnn8 cuda-libraries-11-8 \
|
||||
&& apt install -y cuda-nvcc-12-4 libcublas-12-4 libcudnn8 cuda-libraries-12-4;
|
||||
exit $?
|
||||
else
|
||||
echo "NVIDIA graphics will not be installed on this architecture."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -61,8 +61,6 @@ then
|
||||
sudo apt-get -y install avahi-daemon
|
||||
sed -i 's/'#' - \/var\/run\/dbus/- \/var\/run\/dbus/g' $DOCKER_COMPOSE_YML
|
||||
sed -i 's/'#' - \/var\/run\/avahi-daemon/- \/var\/run\/avahi-daemon/g' $DOCKER_COMPOSE_YML
|
||||
sed -i 's/'#' security_opt:/security_opt:/g' $DOCKER_COMPOSE_YML
|
||||
sed -i 's/'#' - apparmor:unconfined/ - apparmor:unconfined/g' $DOCKER_COMPOSE_YML
|
||||
fi
|
||||
|
||||
echo "Setting permissions on $SCRYPTED_HOME"
|
||||
|
||||
@@ -72,7 +72,6 @@ function removescryptedfstab() {
|
||||
grep -v "scrypted-nvr" /etc/fstab > /tmp/fstab && cp /tmp/fstab /etc/fstab
|
||||
# ensure newline
|
||||
sed -i -e '$a\' /etc/fstab
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
BLOCK_DEVICE="/dev/$1"
|
||||
@@ -96,17 +95,7 @@ then
|
||||
set +e
|
||||
|
||||
sync
|
||||
PARTITION_DEVICE="$BLOCK_DEVICE"1
|
||||
if [ ! -e "$PARTITION_DEVICE" ]
|
||||
then
|
||||
PARTITION_DEVICE="$BLOCK_DEVICE"p1
|
||||
if [ ! -e "$PARTITION_DEVICE" ]
|
||||
then
|
||||
echo "Unable to determine block device partition from block device: $BLOCK_DEVICE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
mkfs -F -t ext4 "$PARTITION_DEVICE"
|
||||
mkfs -F -t ext4 "$BLOCK_DEVICE"1
|
||||
sync
|
||||
|
||||
# parse/evaluate blkid line as env vars
|
||||
@@ -130,7 +119,6 @@ then
|
||||
mkdir -p /mnt/scrypted-nvr
|
||||
echo "PARTLABEL=scrypted-nvr /mnt/scrypted-nvr ext4 defaults,nofail 0 0" >> /etc/fstab
|
||||
mount -a
|
||||
systemctl daemon-reload
|
||||
set +e
|
||||
|
||||
DIR="/mnt/scrypted-nvr"
|
||||
|
||||
@@ -97,7 +97,7 @@ echo "docker compose rm -rf"
|
||||
sudo -u $SERVICE_USER docker rm -f /scrypted /scrypted-watchtower 2> /dev/null
|
||||
|
||||
echo "Installing Scrypted..."
|
||||
RUN sudo -u $SERVICE_USER npx -y scrypted@latest install-server $SCRYPTED_INSTALL_VERSION
|
||||
RUN sudo -u $SERVICE_USER npx -y scrypted@latest install-server
|
||||
|
||||
cat > /etc/systemd/system/scrypted.service <<EOT
|
||||
|
||||
@@ -110,12 +110,10 @@ User=$SERVICE_USER
|
||||
Group=$SERVICE_USER
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/npx -y scrypted serve
|
||||
Restart=always
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
Environment="NODE_OPTIONS=$NODE_OPTIONS"
|
||||
Environment="SCRYPTED_INSTALL_ENVIRONMENT=$SCRYPTED_INSTALL_ENVIRONMENT"
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -121,7 +121,7 @@ then
|
||||
fi
|
||||
|
||||
echo "Installing Scrypted..."
|
||||
RUN $NPX_PATH -y scrypted@latest install-server $SCRYPTED_INSTALL_VERSION
|
||||
RUN $NPX_PATH -y scrypted@latest install-server
|
||||
|
||||
cat > ~/Library/LaunchAgents/app.scrypted.server.plist <<EOT
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
@@ -11,7 +11,7 @@ iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/in
|
||||
choco upgrade -y nodejs-lts --version=20.11.1
|
||||
|
||||
# Install VC Redist, which is necessary for portable python
|
||||
choco install -y vcredist140
|
||||
choco install vcredist140
|
||||
|
||||
# TODO: remove python install, and use portable python
|
||||
# Install Python
|
||||
@@ -26,12 +26,7 @@ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";"
|
||||
py $SCRYPTED_WINDOWS_PYTHON_VERSION -m pip install --upgrade pip
|
||||
py $SCRYPTED_WINDOWS_PYTHON_VERSION -m pip install debugpy typing_extensions typing opencv-python
|
||||
|
||||
$SCRYPTED_INSTALL_VERSION=[System.Environment]::GetEnvironmentVariable("SCRYPTED_INSTALL_VERSION","User")
|
||||
if ($SCRYPTED_INSTALL_VERSION -eq $null) {
|
||||
npx -y scrypted@latest install-server
|
||||
} else {
|
||||
npx -y scrypted@latest install-server $SCRYPTED_INSTALL_VERSION
|
||||
}
|
||||
npx -y scrypted@latest install-server
|
||||
|
||||
$USER_HOME_ESCAPED = $env:USERPROFILE.replace('\', '\\')
|
||||
$SCRYPTED_HOME = $env:USERPROFILE + '\.scrypted'
|
||||
@@ -39,8 +34,7 @@ $SCRYPTED_HOME_ESCAPED_PATH = $SCRYPTED_HOME.replace('\', '\\')
|
||||
npm install --prefix $SCRYPTED_HOME @koush/node-windows --save
|
||||
|
||||
$NPX_PATH = (Get-Command npx).Path
|
||||
# The path needs double quotes to handle spaces in the directory path
|
||||
$NPX_PATH_ESCAPED = '"' + $NPX_PATH.replace('\', '\\') + '"'
|
||||
$NPX_PATH_ESCAPED = $NPX_PATH.replace('\', '\\')
|
||||
|
||||
$SERVICE_JS = @"
|
||||
const fs = require('fs');
|
||||
@@ -50,10 +44,8 @@ try {
|
||||
catch (e) {
|
||||
}
|
||||
const child_process = require('child_process');
|
||||
child_process.spawn('$NPX_PATH_ESCAPED', ['-y', 'scrypted', 'serve'], {
|
||||
child_process.spawn('$($NPX_PATH_ESCAPED)', ['-y', 'scrypted', 'serve'], {
|
||||
stdio: 'inherit',
|
||||
// allow spawning .cmd https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
|
||||
shell: true,
|
||||
});
|
||||
"@
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ function readyn() {
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.96.0
|
||||
SCRYPTED_VERSION=v0.93.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
@@ -41,19 +41,12 @@ pct restore $VMID $SCRYPTED_TAR_ZST $@
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "The Scrypted container installation failed (pct restore error)."
|
||||
echo "pct restore failed"
|
||||
echo ""
|
||||
echo "This may be because the server's 'local' storage device is not being a valid"
|
||||
echo "location for containers."
|
||||
echo "Try running this script again with a different storage device like"
|
||||
echo "'local-lvm' or 'local-zfs'."
|
||||
echo ""
|
||||
echo "#############################################################################"
|
||||
echo "Paste the following command into this shell to install to local-lvm instead:"
|
||||
echo "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 "#############################################################################"
|
||||
echo ""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
#!/bin/bash
|
||||
echo 'if (!process.version.startsWith("v18")) throw new Error("Node 18 is required. Install Node Version Manager (nvm) for versioned node installations. See https://github.com/koush/scrypted/pull/498#issuecomment-1373854020")' | node
|
||||
if [ "$?" != 0 ]
|
||||
then
|
||||
exit
|
||||
fi
|
||||
|
||||
echo ######################################
|
||||
echo "Setting up popular plugins."
|
||||
echo "Additional will need npm install manually."
|
||||
@@ -9,7 +15,7 @@ cd $(dirname $0)
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
for directory in sdk server common packages/client packages/auth-fetch
|
||||
for directory in sdk common server packages/client packages/auth-fetch
|
||||
do
|
||||
echo "$directory > npm install"
|
||||
pushd $directory
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, createHeadersArray, fetcher, getFetchMethod, hasHeader, setDefaultHttpFetchAccept, setHeader } from '../../../server/src/fetch';
|
||||
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, fetcher, getFetchMethod, setDefaultHttpFetchAccept } from '../../../server/src/fetch';
|
||||
|
||||
export interface AuthFetchCredentialState {
|
||||
username: string;
|
||||
@@ -70,19 +70,19 @@ async function getAuth(options: AuthFetchOptions, url: string | URL, method: str
|
||||
|
||||
export function createAuthFetch<B, M>(
|
||||
h: fetcher<B, M>,
|
||||
parser: (body: M, responseType: HttpFetchResponseType | undefined) => Promise<any>
|
||||
parser: (body: M, responseType: HttpFetchResponseType) => Promise<any>
|
||||
) {
|
||||
const authHttpFetch = async <T extends HttpFetchOptions<B>>(options: T & AuthFetchOptions): ReturnType<typeof h<T>> => {
|
||||
const method = getFetchMethod(options);
|
||||
const headers = createHeadersArray(options.headers);
|
||||
const headers = new Headers(options.headers);
|
||||
options.headers = headers;
|
||||
setDefaultHttpFetchAccept(headers, options.responseType);
|
||||
|
||||
const initialHeader = await getAuth(options, options.url, method);
|
||||
// try to provide an authorization if a session exists, but don't override Authorization if provided already.
|
||||
// 401 will trigger a proper auth.
|
||||
if (initialHeader && !hasHeader(headers, 'Authorization'))
|
||||
setHeader(headers, 'Authorization', initialHeader);
|
||||
if (initialHeader && !headers.has('Authorization'))
|
||||
headers.set('Authorization', initialHeader);
|
||||
|
||||
const initialResponse = await h({
|
||||
...options,
|
||||
@@ -99,7 +99,7 @@ export function createAuthFetch<B, M>(
|
||||
};
|
||||
}
|
||||
|
||||
let authenticateHeaders: string | string[] | null = initialResponse.headers.get('www-authenticate');
|
||||
let authenticateHeaders: string | string[] = initialResponse.headers.get('www-authenticate');
|
||||
if (!authenticateHeaders)
|
||||
throw new Error('Did not find WWW-Authenticate header.');
|
||||
|
||||
@@ -126,7 +126,7 @@ export function createAuthFetch<B, M>(
|
||||
|
||||
const header = await getAuth(options, options.url, method);
|
||||
if (header)
|
||||
setHeader(headers, 'Authorization', header);
|
||||
headers.set('Authorization', header);
|
||||
|
||||
return h(options);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"inlineSources": true,
|
||||
"declaration": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
|
||||
18
packages/cli/package-lock.json
generated
18
packages/cli/package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "scrypted",
|
||||
"version": "1.3.16",
|
||||
"version": "1.3.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "scrypted",
|
||||
"version": "1.3.16",
|
||||
"version": "1.3.13",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/client": "^1.3.3",
|
||||
"@scrypted/types": "^0.3.30",
|
||||
"@scrypted/types": "^0.2.99",
|
||||
"engine.io-client": "^6.5.3",
|
||||
"readline-sync": "^1.4.10",
|
||||
"semver": "^7.5.4",
|
||||
@@ -101,11 +101,15 @@
|
||||
"rimraf": "^5.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/client/node_modules/@scrypted/types": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.4.tgz",
|
||||
"integrity": "sha512-k/YMx8lIWOkePgXfKW9POr12mb+erFU2JKxO7TW92GyW8ojUWw9VOc0PK6O9bybi0vhsEnvMFkO6pO6bAonsVA=="
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.3.30",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.30.tgz",
|
||||
"integrity": "sha512-1k+JVSR6WSNmE/5mLdqfrTmV3uRbvZp0OwKb8ikNi39ysBuC000tQGcEdXZqhYqRgWdhDTWtxXe9XsYoAZGKmA==",
|
||||
"license": "ISC"
|
||||
"version": "0.2.99",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.99.tgz",
|
||||
"integrity": "sha512-2J1FH7tpAW5X3rgA70gJ+z0HFM90c/tBA+JXdP1vI1d/0yVmh9TSxnHoCuADN4R2NQXHmoZ6Nbds9kKAQ/25XQ=="
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "scrypted",
|
||||
"version": "1.3.16",
|
||||
"version": "1.3.13",
|
||||
"description": "",
|
||||
"main": "./dist/packages/cli/src/main.js",
|
||||
"bin": {
|
||||
@@ -17,7 +17,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/client": "^1.3.3",
|
||||
"@scrypted/types": "^0.3.30",
|
||||
"@scrypted/types": "^0.2.99",
|
||||
"engine.io-client": "^6.5.3",
|
||||
"readline-sync": "^1.4.10",
|
||||
"semver": "^7.5.4",
|
||||
|
||||
@@ -160,11 +160,11 @@ async function main() {
|
||||
const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(await pendingResult, ScryptedMimeTypes.FFmpegInput);
|
||||
if (ffmpegInput.url && ffmpegInput.urls?.[0]) {
|
||||
const url = new URL(ffmpegInput.url);
|
||||
if (url.hostname === '127.0.0.1' && ffmpegInput.urls?.[0] && ffmpegInput.inputArguments) {
|
||||
ffmpegInput.inputArguments = ffmpegInput.inputArguments.map(i => i === ffmpegInput.url && ffmpegInput.urls ? ffmpegInput.urls?.[0] : i);
|
||||
if (url.hostname === '127.0.0.1' && ffmpegInput.urls?.[0]) {
|
||||
ffmpegInput.inputArguments = ffmpegInput.inputArguments.map(i => i === ffmpegInput.url ? ffmpegInput.urls?.[0] : i);
|
||||
}
|
||||
}
|
||||
const args = ffmpegInput.inputArguments ? [...ffmpegInput.inputArguments] : [];
|
||||
const args = [...ffmpegInput.inputArguments];
|
||||
if (ffmpegInput.h264FilterArguments)
|
||||
args.push(...ffmpegInput.h264FilterArguments);
|
||||
console.log('ffplay', ...args);
|
||||
|
||||
@@ -14,12 +14,8 @@ const EXIT_FILE = '.exit';
|
||||
const UPDATE_FILE = '.update';
|
||||
|
||||
async function runCommand(command: string, ...args: string[]) {
|
||||
if (os.platform() === 'win32') {
|
||||
if (os.platform() === 'win32')
|
||||
command += '.cmd';
|
||||
// wrap each argument in a quote to handle spaces in paths
|
||||
// https://github.com/nodejs/node/issues/38490#issuecomment-927330248
|
||||
args = args.map(arg => '"' + arg + '"');
|
||||
}
|
||||
console.log('running', command, ...args);
|
||||
const cp = child_process.spawn(command, args, {
|
||||
stdio: 'inherit',
|
||||
@@ -28,8 +24,6 @@ async function runCommand(command: string, ...args: string[]) {
|
||||
// https://github.com/lovell/sharp/blob/eefaa998725cf345227d94b40615e090495c6d09/lib/libvips.js#L115C19-L115C46
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS: 'true',
|
||||
},
|
||||
// allow spawning .cmd https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
|
||||
shell: os.platform() === 'win32' ? true : undefined,
|
||||
});
|
||||
await once(cp, 'exit');
|
||||
if (cp.exitCode)
|
||||
@@ -90,13 +84,7 @@ export async function installServe(installVersion: string, ignoreError?: boolean
|
||||
const installJson = path.join(installDir, 'install.json');
|
||||
try {
|
||||
const { version } = JSON.parse(fs.readFileSync(installJson).toString());
|
||||
const processSemver = semver.parse(process.version);
|
||||
if (!processSemver)
|
||||
throw new Error('error parsing process version');
|
||||
const installSemver = semver.parse(version);
|
||||
if (!installSemver)
|
||||
throw new Error('error parsing install.json version');
|
||||
if (processSemver.major !== installSemver.major)
|
||||
if (semver.parse(process.version).major !== semver.parse(version).major)
|
||||
throw new Error('mismatch');
|
||||
}
|
||||
catch (e) {
|
||||
@@ -117,32 +105,16 @@ export async function installServe(installVersion: string, ignoreError?: boolean
|
||||
}
|
||||
|
||||
export async function serveMain(installVersion?: string) {
|
||||
const options = ((): { install: true; version: string } | { install: false } => {
|
||||
if (installVersion) {
|
||||
console.log(`Installing @scrypted/server@${installVersion}`);
|
||||
return {
|
||||
install: true,
|
||||
version: installVersion
|
||||
};
|
||||
}
|
||||
|
||||
if (!fs.existsSync('node_modules/@scrypted/server')) {
|
||||
console.log('Package @scrypted/server not found. Installing.');
|
||||
return {
|
||||
install: true,
|
||||
version: 'latest',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
install: false,
|
||||
}
|
||||
})();
|
||||
let install = !!installVersion;
|
||||
|
||||
const { installDir, volume } = cwdInstallDir();
|
||||
|
||||
if (options.install) {
|
||||
await installServe(options.version, true);
|
||||
if (!fs.existsSync('node_modules/@scrypted/server')) {
|
||||
install = true;
|
||||
installVersion ||= 'latest';
|
||||
console.log('Package @scrypted/server not found. Installing.');
|
||||
}
|
||||
if (install) {
|
||||
await installServe(installVersion, true);
|
||||
}
|
||||
|
||||
// todo: remove at some point after core lxc updater rolls out.
|
||||
@@ -161,7 +133,11 @@ export async function serveMain(installVersion?: string) {
|
||||
|
||||
await startServer(installDir);
|
||||
|
||||
if (fs.existsSync(UPDATE_FILE)) {
|
||||
if (fs.existsSync(EXIT_FILE)) {
|
||||
console.log('Exiting.');
|
||||
process.exit(1);
|
||||
}
|
||||
else if (fs.existsSync(UPDATE_FILE)) {
|
||||
console.log('Update requested. Installing.');
|
||||
await runCommandEatError('npm', '--prefix', installDir, 'install', '--production', '@scrypted/server@latest').catch(e => {
|
||||
console.error('Update failed', e);
|
||||
@@ -169,10 +145,6 @@ export async function serveMain(installVersion?: string) {
|
||||
console.log('Exiting.');
|
||||
process.exit(1);
|
||||
}
|
||||
else if (fs.existsSync(EXIT_FILE)) {
|
||||
console.log('Exiting.');
|
||||
process.exit(1);
|
||||
}
|
||||
else {
|
||||
console.log(`Service unexpectedly exited. Restarting momentarily.`);
|
||||
await sleep(10000);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"inlineSources": true,
|
||||
"declaration": true,
|
||||
"moduleResolution": "Node16",
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
|
||||
2
plugins/alexa/.vscode/settings.json
vendored
2
plugins/alexa/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-server",
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
<details>
|
||||
<summary>Changelog</summary>
|
||||
|
||||
### 0.3.1
|
||||
|
||||
alexa/google-home: fix potential vulnerability. do not allow local network control using cloud tokens belonging to a different user. the plugins are now locked to a specific scrypted cloud account once paired.
|
||||
|
||||
|
||||
### 0.3.0
|
||||
|
||||
alexa/google-home: additional auth token checks to harden endpoints for cloud sharing
|
||||
|
||||
4
plugins/alexa/package-lock.json
generated
4
plugins/alexa/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"uuid": "^9.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.1",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
|
||||
@@ -27,7 +27,6 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
json: true
|
||||
},
|
||||
syncedDevices: {
|
||||
defaultValue: [],
|
||||
multiple: true,
|
||||
hide: true
|
||||
},
|
||||
@@ -67,10 +66,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
alexaHandlers.set('Alexa.Authorization/AcceptGrant', this.onAlexaAuthorization);
|
||||
alexaHandlers.set('Alexa.Discovery/Discover', this.onDiscoverEndpoints);
|
||||
|
||||
this.start()
|
||||
.catch(e => {
|
||||
this.console.error('startup failed', e);
|
||||
})
|
||||
this.start();
|
||||
}
|
||||
|
||||
async start() {
|
||||
|
||||
2
plugins/amcrest/.vscode/launch.json
vendored
2
plugins/amcrest/.vscode/launch.json
vendored
@@ -10,7 +10,7 @@
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"**/plugin-console.*",
|
||||
"**/plugin-remote-worker.*",
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
|
||||
39
plugins/amcrest/package-lock.json
generated
39
plugins/amcrest/package-lock.json
generated
@@ -1,21 +1,19 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.151",
|
||||
"version": "0.0.135",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.151",
|
||||
"version": "0.0.135",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"content-type": "^1.0.5"
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
"@types/node": "^20.10.8"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -25,22 +23,23 @@
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.29",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -78,29 +77,15 @@
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/content-type": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.8.tgz",
|
||||
"integrity": "sha512-1tBhmVUeso3+ahfyaKluXe38p+94lovUZdoVfQ3OnJo9uJC42JT7CBoN3k9HYhAae+GwiBYmHu+N9FZhOG+2Pg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
|
||||
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
|
||||
"version": "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/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.151",
|
||||
"version": "0.0.135",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -36,11 +36,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"content-type": "^1.0.5"
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
"@types/node": "^20.10.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +1,10 @@
|
||||
import { AuthFetchCredentialState, HttpFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { readLine } from '@scrypted/common/src/read-stream';
|
||||
import { parseHeaders, readBody } from '@scrypted/common/src/rtsp-server';
|
||||
import contentType from 'content-type';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { EventEmitter, Readable } from 'stream';
|
||||
import { Destroyable } from '../../rtsp/src/rtsp';
|
||||
import { Readable } from 'stream';
|
||||
import { getDeviceInfo } from './probe';
|
||||
import { Point } from '@scrypted/sdk';
|
||||
|
||||
// Human
|
||||
// {
|
||||
// "Action" : "Cross",
|
||||
// "Class" : "Normal",
|
||||
// "CountInGroup" : 1,
|
||||
// "DetectRegion" : [
|
||||
// [ 455, 260 ],
|
||||
// [ 3586, 260 ],
|
||||
// [ 3768, 7580 ],
|
||||
// [ 382, 7451 ]
|
||||
// ],
|
||||
// "Direction" : "Enter",
|
||||
// "EventID" : 10181,
|
||||
// "GroupID" : 0,
|
||||
// "Name" : "Rule1",
|
||||
// "Object" : {
|
||||
// "Action" : "Appear",
|
||||
// "BoundingBox" : [ 2856, 1280, 3880, 4880 ],
|
||||
// "Center" : [ 3368, 3080 ],
|
||||
// "Confidence" : 0,
|
||||
// "LowerBodyColor" : [ 0, 0, 0, 0 ],
|
||||
// "MainColor" : [ 0, 0, 0, 0 ],
|
||||
// "ObjectID" : 863,
|
||||
// "ObjectType" : "Human",
|
||||
// "RelativeID" : 0,
|
||||
// "Speed" : 0
|
||||
// },
|
||||
// "PTS" : 43380319830.0,
|
||||
// "RuleID" : 2,
|
||||
// "Track" : [],
|
||||
// "UTC" : 1711446999,
|
||||
// "UTCMS" : 701
|
||||
// }
|
||||
|
||||
// Face
|
||||
// {
|
||||
// "CfgRuleId" : 1,
|
||||
// "Class" : "FaceDetection",
|
||||
// "CountInGroup" : 2,
|
||||
// "DetectRegion" : null,
|
||||
// "EventID" : 10360,
|
||||
// "EventSeq" : 6,
|
||||
// "Faces" : [
|
||||
// {
|
||||
// "BoundingBox" : [ 1504, 2336, 1728, 2704 ],
|
||||
// "Center" : [ 1616, 2520 ],
|
||||
// "ObjectID" : 94,
|
||||
// "ObjectType" : "HumanFace",
|
||||
// "RelativeID" : 0
|
||||
// }
|
||||
// ],
|
||||
// "FrameSequence" : 8251212,
|
||||
// "GroupID" : 6,
|
||||
// "Mark" : 0,
|
||||
// "Name" : "FaceDetection",
|
||||
// "Object" : {
|
||||
// "Action" : "Appear",
|
||||
// "BoundingBox" : [ 1504, 2336, 1728, 2704 ],
|
||||
// "Center" : [ 1616, 2520 ],
|
||||
// "Confidence" : 19,
|
||||
// "FrameSequence" : 8251212,
|
||||
// "ObjectID" : 94,
|
||||
// "ObjectType" : "HumanFace",
|
||||
// "RelativeID" : 0,
|
||||
// "SerialUUID" : "",
|
||||
// "Source" : 0.0,
|
||||
// "Speed" : 0,
|
||||
// "SpeedTypeInternal" : 0
|
||||
// },
|
||||
// "Objects" : [
|
||||
// {
|
||||
// "Action" : "Appear",
|
||||
// "BoundingBox" : [ 1504, 2336, 1728, 2704 ],
|
||||
// "Center" : [ 1616, 2520 ],
|
||||
// "Confidence" : 19,
|
||||
// "FrameSequence" : 8251212,
|
||||
// "ObjectID" : 94,
|
||||
// "ObjectType" : "HumanFace",
|
||||
// "RelativeID" : 0,
|
||||
// "SerialUUID" : "",
|
||||
// "Source" : 0.0,
|
||||
// "Speed" : 0,
|
||||
// "SpeedTypeInternal" : 0
|
||||
// }
|
||||
// ],
|
||||
// "PTS" : 43774941350.0,
|
||||
// "Priority" : 0,
|
||||
// "RuleID" : 1,
|
||||
// "RuleId" : 1,
|
||||
// "Source" : -1280470024.0,
|
||||
// "UTC" : 947510337,
|
||||
// "UTCMS" : 0
|
||||
// }
|
||||
export interface AmcrestObjectDetails {
|
||||
Action: string;
|
||||
BoundingBox: Point;
|
||||
Center: Point;
|
||||
Confidence: number;
|
||||
LowerBodyColor: [number, number, number, number];
|
||||
MainColor: [number, number, number, number];
|
||||
ObjectID: number;
|
||||
ObjectType: string;
|
||||
RelativeID: number;
|
||||
Speed: number;
|
||||
}
|
||||
|
||||
export interface AmcrestEventData {
|
||||
Action: string;
|
||||
Class: string;
|
||||
CountInGroup: number;
|
||||
DetectRegion: Point[];
|
||||
Direction: string;
|
||||
EventID: number;
|
||||
GroupID: number;
|
||||
Name: string;
|
||||
Object: AmcrestObjectDetails;
|
||||
PTS: number;
|
||||
RuleID: number;
|
||||
Track: any[];
|
||||
UTC: number;
|
||||
UTCMS: number;
|
||||
}
|
||||
|
||||
export enum AmcrestEvent {
|
||||
MotionStart = "Code=VideoMotion;action=Start",
|
||||
MotionStop = "Code=VideoMotion;action=Stop",
|
||||
MotionInfo = "Code=VideoMotionInfo;action=State",
|
||||
AudioStart = "Code=AudioMutation;action=Start",
|
||||
AudioStop = "Code=AudioMutation;action=Stop",
|
||||
TalkInvite = "Code=_DoTalkAction_;action=Invite",
|
||||
@@ -148,33 +18,8 @@ export enum AmcrestEvent {
|
||||
DahuaTalkHangup = "Code=PassiveHungup;action=Start",
|
||||
DahuaCallDeny = "Code=HungupPhone;action=Pulse",
|
||||
DahuaTalkPulse = "Code=_CallNoAnswer_;action=Pulse",
|
||||
FaceDetection = "Code=FaceDetection;action=Start",
|
||||
SmartMotionHuman = "Code=SmartMotionHuman;action=Start",
|
||||
SmartMotionVehicle = "Code=Vehicle;action=Start",
|
||||
CrossLineDetection = "Code=CrossLineDetection;action=Start",
|
||||
CrossRegionDetection = "Code=CrossRegionDetection;action=Start",
|
||||
}
|
||||
|
||||
|
||||
async function readAmcrestMessage(client: Readable): Promise<string[]> {
|
||||
let currentHeaders: string[] = [];
|
||||
while (true) {
|
||||
const originalLine = await readLine(client);
|
||||
const line = originalLine.trim();
|
||||
if (!line)
|
||||
return currentHeaders;
|
||||
// dahua bugs out and sends message without a newline separating the body:
|
||||
// Content-Length:39
|
||||
// Code=AudioMutation;action=Start;index=0
|
||||
if (!line.includes(':')) {
|
||||
client.unshift(Buffer.from(originalLine + '\n'));
|
||||
return currentHeaders;
|
||||
}
|
||||
currentHeaders.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AmcrestCameraClient {
|
||||
credential: AuthFetchCredentialState;
|
||||
|
||||
@@ -233,8 +78,7 @@ export class AmcrestCameraClient {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async listenEvents(): Promise<Destroyable> {
|
||||
const events = new EventEmitter();
|
||||
async listenEvents() {
|
||||
const url = `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[All]`;
|
||||
console.log('preparing event listener', url);
|
||||
|
||||
@@ -242,119 +86,32 @@ export class AmcrestCameraClient {
|
||||
url,
|
||||
responseType: 'readable',
|
||||
});
|
||||
const stream: IncomingMessage = response.body;
|
||||
(events as any).destroy = () => {
|
||||
stream.destroy();
|
||||
events.removeAllListeners();
|
||||
};
|
||||
stream.on('close', () => {
|
||||
events.emit('close');
|
||||
});
|
||||
stream.on('end', () => {
|
||||
events.emit('end');
|
||||
});
|
||||
stream.on('error', e => {
|
||||
events.emit('error', e);
|
||||
});
|
||||
const stream = response.body;
|
||||
stream.socket.setKeepAlive(true);
|
||||
|
||||
|
||||
const ct = stream.headers['content-type'];
|
||||
// make content type parsable as content disposition filename
|
||||
const cd = contentType.parse(ct);
|
||||
let { boundary } = cd.parameters;
|
||||
// amcrest may send "--myboundary" or "-- myboundary" (with a space)
|
||||
const altBoundary = `-- ${boundary}`;
|
||||
boundary = `--${boundary}`;
|
||||
const boundaryEnd = `${boundary}--`;
|
||||
|
||||
|
||||
(async () => {
|
||||
while (true) {
|
||||
let ignore = await readLine(stream);
|
||||
ignore = ignore.trim();
|
||||
if (!ignore)
|
||||
continue;
|
||||
if (ignore === boundaryEnd)
|
||||
continue;
|
||||
// dahua bugs out and sends this.
|
||||
if (ignore === 'HTTP/1.1 200 OK') {
|
||||
const message = await readAmcrestMessage(stream);
|
||||
this.console.log('ignoring dahua http message', message);
|
||||
message.unshift('');
|
||||
const headers = parseHeaders(message);
|
||||
const body = await readBody(stream, headers);
|
||||
if (body)
|
||||
this.console.log('ignoring dahua http body', body);
|
||||
continue;
|
||||
}
|
||||
if (ignore !== boundary && ignore !== altBoundary) {
|
||||
this.console.error('expected boundary but found', ignore);
|
||||
this.console.error(response.headers);
|
||||
throw new Error('expected boundary');
|
||||
}
|
||||
|
||||
const message = await readAmcrestMessage(stream);
|
||||
events.emit('data', message);
|
||||
message.unshift('');
|
||||
const headers = parseHeaders(message);
|
||||
const body = await readBody(stream, headers);
|
||||
|
||||
const data = body.toString();
|
||||
events.emit('data', data);
|
||||
|
||||
const parts = data.split(';');
|
||||
let index: string;
|
||||
try {
|
||||
for (const part of parts) {
|
||||
if (part.startsWith('index')) {
|
||||
index = part.split('=')[1]?.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.console.error('error parsing index', data);
|
||||
}
|
||||
let jsonData: any;
|
||||
try {
|
||||
for (const part of parts) {
|
||||
if (part.startsWith('data')) {
|
||||
jsonData = JSON.parse(part.split('=')[1]?.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.console.error('error parsing data', data);
|
||||
}
|
||||
|
||||
for (const event of Object.values(AmcrestEvent)) {
|
||||
if (data.indexOf(event) !== -1) {
|
||||
events.emit('event', event, index, data);
|
||||
|
||||
if (event === AmcrestEvent.SmartMotionHuman) {
|
||||
events.emit('smart', 'person', jsonData);
|
||||
}
|
||||
else if (event === AmcrestEvent.SmartMotionVehicle) {
|
||||
events.emit('smart', 'car', jsonData);
|
||||
}
|
||||
else if (event === AmcrestEvent.FaceDetection) {
|
||||
events.emit('smart', 'face', jsonData);
|
||||
}
|
||||
else if (event === AmcrestEvent.CrossLineDetection || event === AmcrestEvent.CrossRegionDetection) {
|
||||
const eventData: AmcrestEventData = jsonData;
|
||||
if (eventData?.Object?.ObjectType === 'Human') {
|
||||
events.emit('smart', 'person', eventData);
|
||||
}
|
||||
else if (eventData?.Object?.ObjectType === 'Vehicle') {
|
||||
events.emit('smart', 'car', eventData);
|
||||
}
|
||||
}
|
||||
stream.on('data', (buffer: Buffer) => {
|
||||
const data = buffer.toString();
|
||||
const parts = data.split(';');
|
||||
let index: string;
|
||||
try {
|
||||
for (const part of parts) {
|
||||
if (part.startsWith('index')) {
|
||||
index = part.split('=')[1]?.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
.catch(() => stream.destroy());
|
||||
return events as any as Destroyable;
|
||||
catch (e) {
|
||||
this.console.error('error parsing index', data);
|
||||
}
|
||||
// this.console?.log('event', data);
|
||||
for (const event of Object.values(AmcrestEvent)) {
|
||||
if (data.indexOf(event) !== -1) {
|
||||
stream.emit('event', event, index, data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
async enableContinousRecording(channel: number) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers';
|
||||
import { readLength } from "@scrypted/common/src/read-stream";
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, Lock, MediaObject, MediaStreamOptions, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, Reboot, RequestPictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, Lock, MediaObject, MediaStreamOptions, Reboot, RequestPictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
|
||||
import child_process, { ChildProcess } from 'child_process';
|
||||
import { PassThrough, Readable, Stream } from "stream";
|
||||
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
|
||||
import { RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
|
||||
import { AmcrestCameraClient, AmcrestEvent, AmcrestEventData } from "./amcrest-api";
|
||||
import { AmcrestCameraClient, AmcrestEvent } from "./amcrest-api";
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
@@ -22,13 +22,12 @@ function findValue(blob: string, prefix: string, key: string) {
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, Lock, VideoRecorder, Reboot, ObjectDetector {
|
||||
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, Lock, VideoRecorder, Reboot {
|
||||
eventStream: Stream;
|
||||
cp: ChildProcess;
|
||||
client: AmcrestCameraClient;
|
||||
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
|
||||
onvifIntercom = new OnvifIntercom(this);
|
||||
hasSmartDetection: boolean;
|
||||
|
||||
constructor(nativeId: string, provider: RtspProvider) {
|
||||
super(nativeId, provider);
|
||||
@@ -37,7 +36,6 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
this.storage.removeItem('amcrestDoorbell');
|
||||
}
|
||||
|
||||
this.hasSmartDetection = this.storage.getItem('hasSmartDetection') === 'true';
|
||||
this.updateDevice();
|
||||
this.updateDeviceInfo();
|
||||
}
|
||||
@@ -186,19 +184,10 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
if (idx.toString() !== channelNumber)
|
||||
return;
|
||||
}
|
||||
if (event === AmcrestEvent.MotionStart
|
||||
|| event === AmcrestEvent.SmartMotionHuman
|
||||
|| event === AmcrestEvent.SmartMotionVehicle
|
||||
|| event === AmcrestEvent.CrossLineDetection
|
||||
|| event === AmcrestEvent.CrossRegionDetection) {
|
||||
if (event === AmcrestEvent.MotionStart) {
|
||||
this.motionDetected = true;
|
||||
resetMotionTimeout();
|
||||
}
|
||||
else if (event === AmcrestEvent.MotionInfo) {
|
||||
// this seems to be a motion pulse
|
||||
if (this.motionDetected)
|
||||
resetMotionTimeout();
|
||||
}
|
||||
else if (event === AmcrestEvent.MotionStop) {
|
||||
// use resetMotionTimeout
|
||||
}
|
||||
@@ -242,43 +231,9 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
}
|
||||
});
|
||||
|
||||
events.on('smart', (className: string, data: AmcrestEventData) => {
|
||||
if (!this.hasSmartDetection) {
|
||||
this.hasSmartDetection = true;
|
||||
this.storage.setItem('hasSmartDetection', 'true');
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
const detected: ObjectsDetected = {
|
||||
timestamp: Date.now(),
|
||||
detections: [
|
||||
{
|
||||
score: 1,
|
||||
className,
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
this.onDeviceEvent(ScryptedInterface.ObjectDetector, detected);
|
||||
});
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
async getDetectionInput(detectionId: string, eventId?: any): Promise<MediaObject> {
|
||||
return;
|
||||
}
|
||||
|
||||
async getObjectTypes(): Promise<ObjectDetectionTypes> {
|
||||
return {
|
||||
classes: [
|
||||
'person',
|
||||
'face',
|
||||
'car',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async getOtherSettings(): Promise<Setting[]> {
|
||||
const ret = await super.getOtherSettings();
|
||||
ret.push(
|
||||
@@ -517,19 +472,13 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
if (isDoorbell || twoWayAudio) {
|
||||
interfaces.push(ScryptedInterface.Intercom);
|
||||
}
|
||||
|
||||
const enableDahuaLock = this.storage.getItem('enableDahuaLock') === 'true';
|
||||
if (isDoorbell && doorbellType === DAHUA_DOORBELL_TYPE && enableDahuaLock) {
|
||||
interfaces.push(ScryptedInterface.Lock);
|
||||
}
|
||||
|
||||
const continuousRecording = this.storage.getItem('continuousRecording') === 'true';
|
||||
if (continuousRecording)
|
||||
interfaces.push(ScryptedInterface.VideoRecorder);
|
||||
|
||||
if (this.hasSmartDetection)
|
||||
interfaces.push(ScryptedInterface.ObjectDetector);
|
||||
|
||||
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
|
||||
}
|
||||
|
||||
@@ -572,7 +521,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
}
|
||||
|
||||
const doorbellType = this.storage.getItem('doorbellType');
|
||||
|
||||
|
||||
// not sure if this all works, since i don't actually have a doorbell.
|
||||
// good luck!
|
||||
const channel = this.getRtspChannel() || '1';
|
||||
@@ -599,22 +548,12 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
}
|
||||
else {
|
||||
args.push(
|
||||
"-vn",
|
||||
'-acodec', 'aac',
|
||||
'-f', 'adts',
|
||||
'pipe:3',
|
||||
);
|
||||
"-vn",
|
||||
'-acodec', 'aac',
|
||||
'-f', 'adts',
|
||||
'pipe:3',
|
||||
);
|
||||
contentType = 'Audio/AAC';
|
||||
// args.push(
|
||||
// "-vn",
|
||||
// '-acodec', 'pcm_mulaw',
|
||||
// '-ac', '1',
|
||||
// '-ar', '8000',
|
||||
// '-sample_fmt', 's16',
|
||||
// '-f', 'mulaw',
|
||||
// 'pipe:3',
|
||||
// );
|
||||
// contentType = 'Audio/G.711A';
|
||||
}
|
||||
|
||||
this.console.log('ffmpeg intercom', args);
|
||||
@@ -634,19 +573,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();
|
||||
const abortController = new AbortController();
|
||||
this.getClient().request({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Content-Length': '9999999',
|
||||
'Content-Length': '9999999'
|
||||
},
|
||||
signal: abortController.signal,
|
||||
responseType: 'readable',
|
||||
}, passthrough)
|
||||
.catch(() => { })
|
||||
.finally(() => this.console.log('request finished'))
|
||||
}, passthrough);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
@@ -658,8 +593,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
}
|
||||
finally {
|
||||
this.console.log('audio finished');
|
||||
passthrough.destroy();
|
||||
abortController.abort();
|
||||
passthrough.end();
|
||||
}
|
||||
|
||||
this.stopIntercom();
|
||||
|
||||
@@ -29,14 +29,9 @@ export async function getDeviceInfo(credential: AuthFetchCredentialState, addres
|
||||
vals[k] = v.trim();
|
||||
}
|
||||
|
||||
const ret = {
|
||||
return {
|
||||
deviceType: vals.deviceType,
|
||||
hardwareVersion: vals.hardwareVersion,
|
||||
serialNumber: vals.serialNumber,
|
||||
};
|
||||
|
||||
if (!ret.deviceType && !ret.hardwareVersion && !ret.serialNumber)
|
||||
throw new Error('not amcrest');
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
534
plugins/bticino/package-lock.json
generated
534
plugins/bticino/package-lock.json
generated
@@ -1,48 +1,52 @@
|
||||
{
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.13",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.13",
|
||||
"dependencies": {
|
||||
"@slyoldfox/sip": "^0.0.6-1",
|
||||
"sdp": "^3.0.3",
|
||||
"stun": "^2.1.0"
|
||||
"stun": "^2.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"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": "^5.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"ts-node": "^10.9.2"
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"version": "0.3.29",
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.2",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -85,18 +89,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"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==",
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
@@ -126,9 +130,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||
"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": {
|
||||
@@ -144,21 +148,27 @@
|
||||
"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==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.18.96",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz",
|
||||
"integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==",
|
||||
"version": "16.18.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz",
|
||||
"integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -168,9 +178,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
@@ -219,18 +229,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -361,22 +365,6 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@@ -394,25 +382,6 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
@@ -433,12 +402,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
@@ -449,29 +415,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -482,26 +432,15 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
@@ -515,17 +454,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
@@ -540,9 +468,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
},
|
||||
"node_modules/ip2buf": {
|
||||
"version": "2.0.0",
|
||||
@@ -558,11 +486,11 @@
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.0"
|
||||
"has": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -743,9 +671,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -861,11 +789,11 @@
|
||||
"integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg=="
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.12.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
|
||||
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
|
||||
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@@ -937,11 +865,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"is-core-module": "^2.9.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
@@ -984,22 +912,6 @@
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -1022,17 +934,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -1053,9 +961,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-exceptions": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
|
||||
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
|
||||
},
|
||||
"node_modules/spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
@@ -1067,9 +975,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.17",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
|
||||
"integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg=="
|
||||
"version": "3.0.13",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
|
||||
"integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w=="
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "1.1.0",
|
||||
@@ -1146,9 +1054,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
@@ -1197,9 +1105,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
|
||||
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
@@ -1207,7 +1115,7 @@
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
@@ -1218,6 +1126,14 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -1277,15 +1193,15 @@
|
||||
}
|
||||
},
|
||||
"@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"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==",
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
@@ -1303,10 +1219,10 @@
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^20.11.0",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
"@types/node": "^16.9.0",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
@@ -1316,7 +1232,7 @@
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -1339,9 +1255,9 @@
|
||||
"integrity": "sha512-PJBIAKS3aMsFTHeQLfAtVpZOduAqGNZZAEH6Kb15htGUcSJWHZ9r2LAjxm3fD4yWT9plYlO0CthcEVnlrrwQLA=="
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node12": {
|
||||
@@ -1357,27 +1273,33 @@
|
||||
"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==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.18.96",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz",
|
||||
"integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==",
|
||||
"version": "16.18.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz",
|
||||
"integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
||||
"dev": true
|
||||
},
|
||||
"arg": {
|
||||
@@ -1414,15 +1336,12 @@
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"requires": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
@@ -1508,16 +1427,6 @@
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
|
||||
},
|
||||
"define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"requires": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@@ -1532,19 +1441,6 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
@@ -1559,9 +1455,9 @@
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.3.1",
|
||||
@@ -1572,23 +1468,13 @@
|
||||
}
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
@@ -1596,32 +1482,19 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"requires": {
|
||||
"es-define-property": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-proto": {
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q=="
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
@@ -1633,9 +1506,9 @@
|
||||
"integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ=="
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
},
|
||||
"ip2buf": {
|
||||
"version": "2.0.0",
|
||||
@@ -1648,11 +1521,11 @@
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||
"requires": {
|
||||
"hasown": "^2.0.0"
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"is-plain-obj": {
|
||||
@@ -1796,9 +1669,9 @@
|
||||
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ=="
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "1.3.0",
|
||||
@@ -1887,11 +1760,11 @@
|
||||
"integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.12.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
|
||||
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
|
||||
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"query-string": {
|
||||
@@ -1939,11 +1812,11 @@
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"requires": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"is-core-module": "^2.9.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
@@ -1963,19 +1836,6 @@
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
|
||||
},
|
||||
"set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"requires": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -1992,14 +1852,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
@@ -2017,9 +1876,9 @@
|
||||
}
|
||||
},
|
||||
"spdx-exceptions": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
|
||||
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
|
||||
},
|
||||
"spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
@@ -2031,9 +1890,9 @@
|
||||
}
|
||||
},
|
||||
"spdx-license-ids": {
|
||||
"version": "3.0.17",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
|
||||
"integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg=="
|
||||
"version": "3.0.13",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
|
||||
"integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w=="
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.1.0",
|
||||
@@ -2083,9 +1942,9 @@
|
||||
"integrity": "sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA=="
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
@@ -2109,9 +1968,9 @@
|
||||
"integrity": "sha512-8yyRd1ZdNp+AQLGqi3lTaA2k81JjlIZOyFQEsi7GQWBgirnQOxjqVtDEbYHM2Z4yFdJ5AQw0fxBLLnDCl6RXoQ=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
|
||||
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
@@ -2120,6 +1979,11 @@
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.13",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
@@ -34,12 +34,14 @@
|
||||
"dependencies": {
|
||||
"@slyoldfox/sip": "^0.0.6-1",
|
||||
"sdp": "^3.0.3",
|
||||
"stun": "^2.1.0"
|
||||
"stun": "^2.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import { createBindUdp, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
||||
import { closeQuiet, createBindUdp, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
import { RtspServer } from '@scrypted/common/src/rtsp-server';
|
||||
import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
|
||||
import sdk, { BinarySensor, Camera, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, Intercom, MediaObject, MediaStreamUrl, MotionSensor, PictureOptions, Reboot, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
|
||||
import sdk, { BinarySensor, Camera, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, Intercom, MediaObject, MediaStreamUrl, MotionSensor, PictureOptions, Reboot, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
|
||||
import { SipCallSession } from '../../sip/src/sip-call-session';
|
||||
import { RtpDescription, getPayloadType, getSequenceNumber, isRtpMessagePayloadType, isStunMessage } from '../../sip/src/rtp-utils';
|
||||
import { VoicemailHandler } from './bticino-voicemailHandler';
|
||||
import { CompositeSipMessageHandler } from '../../sip/src/compositeSipMessageHandler';
|
||||
import { SipHelper } from './sip-helper';
|
||||
import child_process, { ChildProcess } from 'child_process';
|
||||
import dgram from 'dgram';
|
||||
import { BticinoStorageSettings } from './storage-settings';
|
||||
import { BticinoSipPlugin } from './main';
|
||||
import { BticinoSipLock } from './bticino-lock';
|
||||
import { safePrintFFmpegArguments } from '@scrypted/common/src/media-helpers';
|
||||
import { ffmpegLogInitialOutput, safeKillFFmpeg, safePrintFFmpegArguments } from '@scrypted/common/src/media-helpers';
|
||||
import { PersistentSipManager } from './persistent-sip-manager';
|
||||
import { InviteHandler } from './bticino-inviteHandler';
|
||||
import { SipOptions, SipRequest } from '../../sip/src/sip-manager';
|
||||
import { startRtpForwarderProcess } from '../../webrtc/src/rtp-forwarders';
|
||||
import fs from "fs"
|
||||
import url from "url"
|
||||
import path from 'path';
|
||||
import { default as stream } from 'node:stream'
|
||||
import type { ReadableStream } from 'node:stream/web'
|
||||
import { finished } from "stream/promises";
|
||||
|
||||
import { get } from 'http'
|
||||
import { ControllerApi } from './c300x-controller-api';
|
||||
@@ -31,13 +25,13 @@ import { BticinoMuteSwitch } from './bticino-mute-switch';
|
||||
|
||||
const STREAM_TIMEOUT = 65000;
|
||||
const { mediaManager } = sdk;
|
||||
const BTICINO_CLIPS = path.join(process.env.SCRYPTED_PLUGIN_VOLUME, 'bticino-clips');
|
||||
|
||||
export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor, DeviceProvider, Intercom, Camera, VideoCamera, Settings, BinarySensor, HttpRequestHandler, VideoClips, Reboot {
|
||||
|
||||
private session: SipCallSession
|
||||
private remoteRtpDescription: Promise<RtpDescription>
|
||||
private forwarder
|
||||
private audioOutForwarder: dgram.Socket
|
||||
private audioOutProcess: ChildProcess
|
||||
private refreshTimeout: NodeJS.Timeout
|
||||
public requestHandlers: CompositeSipMessageHandler = new CompositeSipMessageHandler()
|
||||
public incomingCallRequest : SipRequest
|
||||
@@ -153,87 +147,11 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
});
|
||||
}
|
||||
|
||||
async getVideoClip(videoId: string): Promise<MediaObject> {
|
||||
const outputfile = await this.fetchAndConvertVoicemailMessage(videoId);
|
||||
|
||||
const fileURLToPath: string = url.pathToFileURL(outputfile).toString()
|
||||
this.console.log(`Creating mediaObject for url: ${fileURLToPath}`)
|
||||
return await mediaManager.createMediaObjectFromUrl(fileURLToPath);
|
||||
}
|
||||
|
||||
private async fetchAndConvertVoicemailMessage(videoId: string) {
|
||||
getVideoClip(videoId: string): Promise<MediaObject> {
|
||||
let c300x = SipHelper.getIntercomIp(this)
|
||||
|
||||
const response = await fetch(`http://${c300x}:8080/voicemail?msg=${videoId}/aswm.avi&raw=true`);
|
||||
|
||||
const contentLength: number = Number(response.headers.get("Content-Length"));
|
||||
const lastModified: Date = new Date(response.headers.get("Last-Modified-Time"));
|
||||
|
||||
const avifile = `${BTICINO_CLIPS}/${videoId}.avi`;
|
||||
const outputfile = `${BTICINO_CLIPS}/${videoId}.mp4`;
|
||||
|
||||
if (!fs.existsSync(BTICINO_CLIPS)) {
|
||||
this.console.log(`Creating clips dir at: ${BTICINO_CLIPS}`)
|
||||
fs.mkdirSync(BTICINO_CLIPS);
|
||||
}
|
||||
|
||||
if (fs.existsSync(avifile)) {
|
||||
const stat = fs.statSync(avifile);
|
||||
if (stat.size != contentLength || stat.mtime.getTime() != lastModified.getTime()) {
|
||||
this.console.log(`Size ${stat.size} != ${contentLength} or time ${stat.mtime.getTime} != ${lastModified.getTime}`)
|
||||
try {
|
||||
fs.rmSync(avifile);
|
||||
} catch (e) { }
|
||||
try {
|
||||
fs.rmSync(outputfile);
|
||||
} catch (e) { }
|
||||
} else {
|
||||
this.console.log(`Keeping the cached video at ${avifile}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(avifile)) {
|
||||
this.console.log("Starting download.")
|
||||
await finished(stream.Readable.from(response.body as ReadableStream<Uint8Array>).pipe(fs.createWriteStream(avifile)));
|
||||
this.console.log("Download finished.")
|
||||
try {
|
||||
this.console.log(`Setting mtime to ${lastModified}`)
|
||||
fs.utimesSync(avifile, lastModified, lastModified);
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
const ffmpegPath = await mediaManager.getFFmpegPath();
|
||||
const ffmpegArgs = [
|
||||
'-hide_banner',
|
||||
'-nostats',
|
||||
'-y',
|
||||
'-i', avifile,
|
||||
outputfile
|
||||
];
|
||||
|
||||
safePrintFFmpegArguments(console, ffmpegArgs);
|
||||
const cp = child_process.spawn(ffmpegPath, ffmpegArgs, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const p = new Promise((resolveFunc) => {
|
||||
cp.stdout.on("data", (x) => {
|
||||
this.console.log(x.toString());
|
||||
});
|
||||
cp.stderr.on("data", (x) => {
|
||||
this.console.error(x.toString());
|
||||
});
|
||||
cp.on("exit", (code) => {
|
||||
resolveFunc(code);
|
||||
});
|
||||
});
|
||||
|
||||
let returnCode = await p;
|
||||
|
||||
this.console.log(`Converted file returned code: ${returnCode}`);
|
||||
return outputfile;
|
||||
const url = `http://${c300x}:8080/voicemail?msg=${videoId}/aswm.avi&raw=true`;
|
||||
return mediaManager.createMediaObjectFromUrl(url);
|
||||
}
|
||||
|
||||
getVideoClipThumbnail(thumbnailId: string): Promise<MediaObject> {
|
||||
let c300x = SipHelper.getIntercomIp(this)
|
||||
const url = `http://${c300x}:8080/voicemail?msg=${thumbnailId}/aswm.jpg&raw=true`;
|
||||
@@ -275,27 +193,21 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
}
|
||||
|
||||
async takePicture(option?: PictureOptions): Promise<MediaObject> {
|
||||
let rebroadcastEnabled = this.interfaces?.includes( "mixin:@scrypted/prebuffer-mixin")
|
||||
if( rebroadcastEnabled ) {
|
||||
const thumbnailCacheTime : number = parseInt( this.storage?.getItem('thumbnailCacheTime') ) * 1000 || 300000
|
||||
const now = new Date().getTime()
|
||||
if( !this.lastImageRefresh || this.lastImageRefresh + thumbnailCacheTime < now ) {
|
||||
// get a proxy object to make sure we pass prebuffer when already watching a stream
|
||||
let cam : VideoCamera = sdk.systemManager.getDeviceById<VideoCamera>(this.id)
|
||||
let vs : MediaObject = await cam.getVideoStream()
|
||||
let buf : Buffer = await mediaManager.convertMediaObjectToBuffer(vs, 'image/jpeg');
|
||||
this.cachedImage = buf
|
||||
this.lastImageRefresh = new Date().getTime()
|
||||
this.console.log(`Camera picture updated and cached: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
|
||||
} else {
|
||||
this.console.log(`Not refreshing camera picture: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
}
|
||||
|
||||
return mediaManager.createMediaObject(this.cachedImage, 'image/jpeg')
|
||||
const thumbnailCacheTime : number = parseInt( this.storage?.getItem('thumbnailCacheTime') ) * 1000 || 300000
|
||||
const now = new Date().getTime()
|
||||
if( !this.lastImageRefresh || this.lastImageRefresh + thumbnailCacheTime < now ) {
|
||||
// get a proxy object to make sure we pass prebuffer when already watching a stream
|
||||
let cam : VideoCamera = sdk.systemManager.getDeviceById<VideoCamera>(this.id)
|
||||
let vs : MediaObject = await cam.getVideoStream()
|
||||
let buf : Buffer = await mediaManager.convertMediaObjectToBuffer(vs, 'image/jpeg');
|
||||
this.cachedImage = buf
|
||||
this.lastImageRefresh = new Date().getTime()
|
||||
this.console.log(`Camera picture updated and cached: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
|
||||
} else {
|
||||
throw new Error("To enable snapshots, enable rebroadcast plugin or set a Snapshot URL in the Snapshot plugin to an external image.");
|
||||
this.console.log(`Not refreshing camera picture: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
}
|
||||
return mediaManager.createMediaObject(this.cachedImage, 'image/jpeg')
|
||||
}
|
||||
|
||||
async getPictureOptions(): Promise<PictureOptions[]> {
|
||||
@@ -322,31 +234,52 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
this.session = await this.callIntercom( cleanup )
|
||||
}
|
||||
|
||||
|
||||
this.stopIntercom();
|
||||
|
||||
const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(media, ScryptedMimeTypes.FFmpegInput);
|
||||
const ffmpegInput: FFmpegInput = JSON.parse((await mediaManager.convertMediaObjectToBuffer(media, ScryptedMimeTypes.FFmpegInput)).toString());
|
||||
|
||||
const audioOutForwarder = await createBindZero()
|
||||
this.audioOutForwarder = audioOutForwarder.server
|
||||
let address = (await this.remoteRtpDescription).address
|
||||
this.forwarder = await startRtpForwarderProcess(this.console, ffmpegInput, {
|
||||
audio: {
|
||||
codecCopy: 'speex',
|
||||
encoderArguments: [
|
||||
'-vn', '-sn', '-dn',
|
||||
'-acodec', 'speex',
|
||||
'-flags', '+global_header',
|
||||
'-ac', '1',
|
||||
'-ar', '8k',
|
||||
'-f', 'rtp',
|
||||
],
|
||||
onRtp: rtp => {
|
||||
this.session?.audioSplitter?.send(rtp, 40004, address)
|
||||
}
|
||||
}
|
||||
audioOutForwarder.server.on('message', message => {
|
||||
if( this.session )
|
||||
this.session.audioSplitter.send(message, 40004, address)
|
||||
return null
|
||||
});
|
||||
|
||||
const args = ffmpegInput.inputArguments.slice();
|
||||
args.push(
|
||||
'-vn', '-dn', '-sn',
|
||||
'-acodec', 'speex',
|
||||
'-flags', '+global_header',
|
||||
'-ac', '1',
|
||||
'-ar', '8k',
|
||||
'-f', 'rtp',
|
||||
//'-srtp_out_suite', 'AES_CM_128_HMAC_SHA1_80',
|
||||
//'-srtp_out_params', encodeSrtpOptions(this.decodedSrtpOptions),
|
||||
`rtp://127.0.0.1:${audioOutForwarder.port}?pkt_size=188`,
|
||||
);
|
||||
|
||||
this.console.log("===========================================")
|
||||
safePrintFFmpegArguments( this.console, args )
|
||||
this.console.log("===========================================")
|
||||
|
||||
const cp = child_process.spawn(await mediaManager.getFFmpegPath(), args);
|
||||
ffmpegLogInitialOutput(this.console, cp)
|
||||
this.audioOutProcess = cp;
|
||||
cp.on('exit', () => this.console.log('two way audio ended'));
|
||||
this.session.onCallEnded.subscribe(() => {
|
||||
closeQuiet(audioOutForwarder.server);
|
||||
safeKillFFmpeg(cp)
|
||||
});
|
||||
}
|
||||
|
||||
async stopIntercom(): Promise<void> {
|
||||
this.forwarder?.kill()
|
||||
this.forwarder = undefined
|
||||
closeQuiet(this.audioOutForwarder)
|
||||
this.audioOutProcess?.kill('SIGKILL')
|
||||
this.audioOutProcess = undefined
|
||||
this.audioOutForwarder = undefined
|
||||
}
|
||||
|
||||
resetStreamTimeout() {
|
||||
@@ -556,24 +489,12 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
// Call the C300X
|
||||
this.remoteRtpDescription = sip.callOrAcceptInvite(
|
||||
( audio ) => {
|
||||
let audioSection = [
|
||||
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
|
||||
`m=audio 65000 RTP/SAVP 110`,
|
||||
`a=rtpmap:110 speex/8000`,
|
||||
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
|
||||
]
|
||||
if( !this.incomingCallRequest ) {
|
||||
let DEVADDR = this.storage.getItem('DEVADDR');
|
||||
if( DEVADDR ) {
|
||||
audioSection.unshift('a=DEVADDR:' + DEVADDR)
|
||||
} else {
|
||||
if( sipOptions.to.toLocaleLowerCase().indexOf('c300x') >= 0 || sipOptions.to.toLocaleLowerCase().indexOf('c100x') >= 0 ) {
|
||||
// Needed for bt_answering_machine (bticino specific), to check for c100X
|
||||
audioSection.unshift('a=DEVADDR:20')
|
||||
}
|
||||
}
|
||||
}
|
||||
return audioSection
|
||||
return [
|
||||
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
|
||||
`m=audio 65000 RTP/SAVP 110`,
|
||||
`a=rtpmap:110 speex/8000`,
|
||||
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
|
||||
]
|
||||
}, ( video ) => {
|
||||
return [
|
||||
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
|
||||
|
||||
@@ -33,10 +33,8 @@ export class VoicemailHandler extends SipRequestHandler {
|
||||
handle(request: SipRequest) {
|
||||
const lastVoicemailMessageTimestamp : number = Number.parseInt( this.sipCamera.storage.getItem('lastVoicemailMessageTimestamp') ) || -1
|
||||
const message : string = request.content.toString()
|
||||
let matches : Array<RegExpMatchArray> = [...message.matchAll(/\*#8\*\*40\*([01])\*([01])\*/gm)]
|
||||
if( matches && matches.length > 0 && matches[0].length > 0 ) {
|
||||
this.sipCamera.console.debug( "Answering machine state: " + matches[0][1] + " / Welcome message state: " + matches[0][2] );
|
||||
this.aswmIsEnabled = matches[0][1] == '1';
|
||||
if( message.startsWith('*#8**40*0*0*') || message.startsWith('*#8**40*1*0*') ) {
|
||||
this.aswmIsEnabled = message.startsWith('*#8**40*1*0*');
|
||||
if( this.isEnabled() ) {
|
||||
this.sipCamera.console.debug("Handling incoming answering machine reply")
|
||||
const messages : string[] = message.split(';')
|
||||
@@ -62,8 +60,6 @@ export class VoicemailHandler extends SipRequestHandler {
|
||||
this.sipCamera.console.debug("No new messages since: " + lastVoicemailMessageTimestamp + " lastMessage: " + lastMessageTimestamp)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sipCamera.console.debug("Not handling message: " + message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
plugins/chromecast/package-lock.json
generated
4
plugins/chromecast/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/chromecast",
|
||||
"version": "0.1.58",
|
||||
"version": "0.1.57",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/chromecast",
|
||||
"version": "0.1.58",
|
||||
"version": "0.1.57",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/chromecast",
|
||||
"version": "0.1.58",
|
||||
"version": "0.1.57",
|
||||
"description": "Send video, audio, and text to speech notifications to Chromecast and Google Home devices",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -183,7 +183,7 @@ class CastDevice extends ScryptedDeviceBase implements MediaPlayer, Refresh, Eng
|
||||
media = await mediaManager.createMediaObjectFromUrl(media);
|
||||
}
|
||||
}
|
||||
else if (options?.mimeType?.startsWith('image/') || options?.mimeType?.startsWith('audio/')) {
|
||||
else if (options?.mimeType?.startsWith('image/')) {
|
||||
url = await mediaManager.convertMediaObjectToInsecureLocalUrl(media, options?.mimeType);
|
||||
}
|
||||
|
||||
|
||||
1278
plugins/cloud/package-lock.json
generated
1278
plugins/cloud/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"bpmux": "^8.2.1",
|
||||
"cloudflared": "^0.5.2",
|
||||
"cloudflared": "^0.4.0",
|
||||
"exponential-backoff": "^3.1.1",
|
||||
"http-proxy": "^1.18.1",
|
||||
"nat-upnp": "file:./external/node-nat-upnp"
|
||||
@@ -51,7 +51,7 @@
|
||||
"@types/http-proxy": "^1.17.14",
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/nat-upnp": "^1.1.5",
|
||||
"@types/node": "^20.14.6"
|
||||
"@types/node": "^20.11.19"
|
||||
},
|
||||
"version": "0.2.15"
|
||||
"version": "0.2.13"
|
||||
}
|
||||
|
||||
@@ -531,9 +531,8 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
throw new Error('@scrypted/cloud is not logged in.');
|
||||
const q = qsstringify({
|
||||
scope: local.pathname,
|
||||
serverId: this.storageSettings.values.serverId,
|
||||
ttl,
|
||||
});
|
||||
})
|
||||
const scope = await httpFetch({
|
||||
url: `https://${this.getHostname()}/_punch/scope?${q}`,
|
||||
headers: {
|
||||
@@ -952,13 +951,13 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
|
||||
async startCloudflared() {
|
||||
if (!this.storageSettings.values.cloudflareEnabled) {
|
||||
this.console.log('cloudflared is disabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
if (!this.storageSettings.values.cloudflareEnabled) {
|
||||
this.console.log('cloudflared is disabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.console.log('starting cloudflared');
|
||||
this.cloudflared = await backOff(async () => {
|
||||
const pluginVolume = process.env.SCRYPTED_PLUGIN_VOLUME;
|
||||
@@ -1058,13 +1057,12 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
maxDelay: 300000,
|
||||
});
|
||||
|
||||
await once(this.cloudflared.child, 'exit').catch(() => { });
|
||||
// the successfully started cloudflared process may exit at some point, loop and allow it to restart.
|
||||
this.console.error('cloudflared exited');
|
||||
await once(this.cloudflared.child, 'exit');
|
||||
throw new Error('cloudflared exited.');
|
||||
}
|
||||
catch (e) {
|
||||
// this error may be reached if the cloudflared backoff fails.
|
||||
this.console.error('cloudflared error', e);
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
this.cloudflared = undefined;
|
||||
|
||||
@@ -15,8 +15,6 @@ Environment="SCRYPTED_PYTHON39_PATH=/usr/bin/python3.9"
|
||||
Environment="SCRYPTED_PYTHON310_PATH=/usr/bin/python3.10"
|
||||
Environment="SCRYPTED_FFMPEG_PATH=/usr/bin/ffmpeg"
|
||||
Environment="SCRYPTED_INSTALL_ENVIRONMENT=lxc"
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.28",
|
||||
"version": "0.3.18",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.28",
|
||||
"version": "0.3.18",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.28",
|
||||
"version": "0.3.18",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -18,7 +18,7 @@ const { systemManager, deviceManager, endpointManager } = sdk;
|
||||
export function getAddresses() {
|
||||
const addresses: string[] = [];
|
||||
for (const [iface, nif] of Object.entries(os.networkInterfaces())) {
|
||||
if (iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan') || iface.startsWith('net')) {
|
||||
if (iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')) {
|
||||
addresses.push(iface);
|
||||
addresses.push(...nif.map(addr => addr.address));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import fs from 'fs';
|
||||
import child_process from 'child_process';
|
||||
import { once } from 'events';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import { stdout } from 'process';
|
||||
|
||||
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC = 'lxc';
|
||||
|
||||
@@ -12,7 +11,7 @@ export async function checkLxcDependencies() {
|
||||
|
||||
let needRestart = false;
|
||||
if (!process.version.startsWith('v20.')) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'apt update -y && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt install -y nodejs']);
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt install -y nodejs']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install Node.js 20.x.');
|
||||
@@ -42,37 +41,6 @@ export async function checkLxcDependencies() {
|
||||
sdk.log.a('Failed to daemon-reload systemd.');
|
||||
}
|
||||
|
||||
try {
|
||||
// intel opencl icd is broken from their official apt repos on kernel versions 6.8, which ships with ubuntu 24.04 and proxmox 8.2.
|
||||
// the intel apt repo has not been updated yet.
|
||||
// the current workaround is to install the release manually.
|
||||
// https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
|
||||
if (err)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
}));
|
||||
|
||||
if (
|
||||
// apt
|
||||
output.includes('Version: 23')
|
||||
// was installed via script at some point
|
||||
|| output.includes('Version: 24.13.29138.7')
|
||||
// current script version: 24.17.29377.6
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install intel-opencl-icd.');
|
||||
else
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
sdk.log.a('Failed to verify/install intel-opencl-icd version.');
|
||||
}
|
||||
|
||||
if (needRestart)
|
||||
sdk.log.a('A system update is pending. Please restart Scrypted to apply changes.');
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"target": "ES2021",
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
|
||||
@@ -26,7 +26,6 @@ export function loginScrypted(username: string, password: string, change_passwor
|
||||
username,
|
||||
password,
|
||||
change_password,
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -161,10 +161,10 @@ export default {
|
||||
let t = ``;
|
||||
let toffset = 0;
|
||||
if (detection.score && detection.className !== 'motion') {
|
||||
t += `<tspan x='${x}' dy='${toffset}em'>${Math.round((detection.labelScore || detection.score) * 100) / 100}</tspan>`
|
||||
t += `<tspan x='${x}' dy='${toffset}em'>${Math.round(detection.score * 100) / 100}</tspan>`
|
||||
toffset -= 1.2;
|
||||
}
|
||||
const tname = (detection.label || detection.className) + (detection.id ? `: ${detection.id}` : '')
|
||||
const tname = detection.className + (detection.id ? `: ${detection.id}` : '')
|
||||
t += `<tspan x='${x}' dy='${toffset}em'>${tname}</tspan>`
|
||||
|
||||
const fs = 20;
|
||||
|
||||
@@ -57,10 +57,7 @@ export default {
|
||||
t += `<tspan x='${x}' dy='${toffset}em'>${Math.round(detection.score * 100) / 100}</tspan>`
|
||||
toffset -= 1.2;
|
||||
}
|
||||
const tname = detection.className
|
||||
+ (detection.id || detection.label ? ':' : '')
|
||||
+ (detection.id ? ` ${detection.id}` : '')
|
||||
+ (detection.label ? ` ${detection.label}` : '')
|
||||
const tname = detection.className + (detection.id ? `: ${detection.id}` : '')
|
||||
t += `<tspan x='${x}' dy='${toffset}em'>${tname}</tspan>`
|
||||
|
||||
const fs = 30 * svgScale;
|
||||
|
||||
10
plugins/coreml/package-lock.json
generated
10
plugins/coreml/package-lock.json
generated
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.65",
|
||||
"version": "0.1.29",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.65",
|
||||
"version": "0.1.29",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.31",
|
||||
"version": "0.2.101",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -65,7 +65,7 @@
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"Settings",
|
||||
"DeviceProvider",
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview"
|
||||
]
|
||||
@@ -42,5 +41,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.65"
|
||||
"version": "0.1.29"
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../openvino/src/common
|
||||
@@ -1,49 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import os
|
||||
import re
|
||||
from typing import Any, List, Tuple
|
||||
from typing import Any, Tuple
|
||||
|
||||
import coremltools as ct
|
||||
import scrypted_sdk
|
||||
from PIL import Image
|
||||
from scrypted_sdk import Setting, SettingValue
|
||||
|
||||
from common import yolo
|
||||
from coreml.face_recognition import CoreMLFaceRecognition
|
||||
import yolo
|
||||
from predict import Prediction, PredictPlugin, Rectangle
|
||||
|
||||
try:
|
||||
from coreml.text_recognition import CoreMLTextRecognition
|
||||
except:
|
||||
CoreMLTextRecognition = None
|
||||
from predict import Prediction, PredictPlugin
|
||||
from predict.rectangle import Rectangle
|
||||
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(1, "CoreML-Predict")
|
||||
|
||||
availableModels = [
|
||||
"Default",
|
||||
"scrypted_yolov10m_320",
|
||||
"scrypted_yolov10n_320",
|
||||
"scrypted_yolo_nas_s_320",
|
||||
"scrypted_yolov9e_320",
|
||||
"scrypted_yolov9c_320",
|
||||
"scrypted_yolov9s_320",
|
||||
"scrypted_yolov9t_320",
|
||||
"scrypted_yolov6n_320",
|
||||
"scrypted_yolov6s_320",
|
||||
"scrypted_yolov8n_320",
|
||||
"ssdlite_mobilenet_v2",
|
||||
"yolov4-tiny",
|
||||
]
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(8, "CoreML-Predict")
|
||||
|
||||
|
||||
def parse_label_contents(contents: str):
|
||||
lines = contents.split(",")
|
||||
lines = [line for line in lines if line.strip()]
|
||||
lines = contents.splitlines()
|
||||
ret = {}
|
||||
for row_number, content in enumerate(lines):
|
||||
pair = re.split(r"[:\s]+", content.strip(), maxsplit=1)
|
||||
@@ -54,51 +29,40 @@ def parse_label_contents(contents: str):
|
||||
return ret
|
||||
|
||||
|
||||
def parse_labels(userDefined):
|
||||
yolo = userDefined.get("names") or userDefined.get("yolo.names")
|
||||
if yolo:
|
||||
j = ast.literal_eval(yolo)
|
||||
ret = {}
|
||||
for k, v in j.items():
|
||||
ret[int(k)] = v
|
||||
return ret
|
||||
|
||||
classes = userDefined.get("classes")
|
||||
if not classes:
|
||||
raise Exception("no classes found in model metadata")
|
||||
return parse_label_contents(classes)
|
||||
|
||||
|
||||
class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProvider):
|
||||
class CoreMLPlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
if model == "Default" or model not in availableModels:
|
||||
if model != "Default":
|
||||
self.storage.setItem("model", "Default")
|
||||
model = "scrypted_yolov9c_320"
|
||||
if model == "Default":
|
||||
model = "yolov8n_320"
|
||||
self.yolo = "yolo" in model
|
||||
self.scrypted_yolov10n = "scrypted_yolov10" in model
|
||||
self.scrypted_yolo_nas = "scrypted_yolo_nas" in model
|
||||
self.scrypted_yolo = "scrypted_yolo" in model
|
||||
self.scrypted_model = "scrypted" in model
|
||||
model_version = "v8"
|
||||
mlmodel = "model" if self.scrypted_yolo else model
|
||||
self.yolov8 = "yolov8" in model
|
||||
self.yolov9 = "yolov9" in model
|
||||
model_version = "v2"
|
||||
|
||||
print(f"model: {model}")
|
||||
|
||||
if not self.yolo:
|
||||
# todo convert these to mlpackage
|
||||
labelsFile = self.downloadFile(
|
||||
f"https://github.com/koush/coreml-models/raw/main/{model}/coco_labels.txt",
|
||||
"coco_labels.txt",
|
||||
)
|
||||
modelFile = self.downloadFile(
|
||||
f"https://github.com/koush/coreml-models/raw/main/{model}/{mlmodel}.mlmodel",
|
||||
f"https://github.com/koush/coreml-models/raw/main/{model}/{model}.mlmodel",
|
||||
f"{model}.mlmodel",
|
||||
)
|
||||
else:
|
||||
if self.scrypted_yolo:
|
||||
if self.yolov8:
|
||||
modelFile = self.downloadFile(
|
||||
f"https://github.com/koush/coreml-models/raw/main/{model}/{model}.mlmodel",
|
||||
f"{model}.mlmodel",
|
||||
)
|
||||
elif self.yolov9:
|
||||
files = [
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{mlmodel}.mlmodel",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{model}.mlmodel",
|
||||
f"{model}/{model}.mlpackage/Manifest.json",
|
||||
]
|
||||
|
||||
@@ -113,7 +77,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/FeatureDescriptions.json",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/Metadata.json",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{mlmodel}.mlmodel",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{model}.mlmodel",
|
||||
f"{model}/{model}.mlpackage/Manifest.json",
|
||||
]
|
||||
|
||||
@@ -124,64 +88,25 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
)
|
||||
modelFile = os.path.dirname(p)
|
||||
|
||||
labelsFile = self.downloadFile(
|
||||
f"https://github.com/koush/coreml-models/raw/main/{model}/coco_80cl.txt",
|
||||
f"{model_version}/{model}/coco_80cl.txt",
|
||||
)
|
||||
|
||||
self.model = ct.models.MLModel(modelFile)
|
||||
|
||||
self.modelspec = self.model.get_spec()
|
||||
self.inputdesc = self.modelspec.description.input[0]
|
||||
self.inputheight = self.inputdesc.type.imageType.height
|
||||
self.inputwidth = self.inputdesc.type.imageType.width
|
||||
self.input_name = self.model.get_spec().description.input[0].name
|
||||
|
||||
self.labels = parse_labels(self.modelspec.description.metadata.userDefined)
|
||||
labels_contents = open(labelsFile, "r").read()
|
||||
self.labels = parse_label_contents(labels_contents)
|
||||
# csv in mobilenet model
|
||||
# self.modelspec.description.metadata.userDefined['classes']
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.minThreshold = 0.2
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
devices = [
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "CoreML Face Recognition",
|
||||
},
|
||||
]
|
||||
|
||||
if CoreMLTextRecognition:
|
||||
devices.append(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "CoreML Text Recognition",
|
||||
},
|
||||
)
|
||||
|
||||
await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
{
|
||||
"devices": devices,
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
async def getDevice(self, nativeId: str) -> Any:
|
||||
if nativeId == "facerecognition":
|
||||
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(nativeId)
|
||||
return self.faceDevice
|
||||
if nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or CoreMLTextRecognition(nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
return [
|
||||
@@ -189,7 +114,14 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
"key": "model",
|
||||
"title": "Model",
|
||||
"description": "The detection model used to find objects.",
|
||||
"choices": availableModels,
|
||||
"choices": [
|
||||
"Default",
|
||||
"ssdlite_mobilenet_v2",
|
||||
"yolov4-tiny",
|
||||
"yolov8n",
|
||||
"yolov8n_320",
|
||||
"yolov9c_320",
|
||||
],
|
||||
"value": model,
|
||||
},
|
||||
]
|
||||
@@ -206,34 +138,22 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
def get_input_size(self) -> Tuple[float, float]:
|
||||
return (self.inputwidth, self.inputheight)
|
||||
|
||||
async def detect_batch(self, inputs: List[Any]) -> List[Any]:
|
||||
out_dicts = await asyncio.get_event_loop().run_in_executor(
|
||||
predictExecutor, lambda: self.model.predict(inputs)
|
||||
)
|
||||
return out_dicts
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
objs = []
|
||||
|
||||
# run in executor if this is the plugin loop
|
||||
if self.yolo:
|
||||
out_dict = await self.queue_batch({self.input_name: input})
|
||||
input_name = "image" if self.yolov8 or self.yolov9 else "input_1"
|
||||
if asyncio.get_event_loop() is self.loop:
|
||||
out_dict = await asyncio.get_event_loop().run_in_executor(
|
||||
predictExecutor, lambda: self.model.predict({input_name: input})
|
||||
)
|
||||
else:
|
||||
out_dict = self.model.predict({input_name: input})
|
||||
|
||||
if self.scrypted_yolov10n:
|
||||
if self.yolov8 or self.yolov9:
|
||||
results = list(out_dict.values())[0][0]
|
||||
objs = yolo.parse_yolov10(results)
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
|
||||
if self.scrypted_yolo_nas:
|
||||
predictions = list(out_dict.values())
|
||||
objs = yolo.parse_yolo_nas(predictions)
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
|
||||
if self.scrypted_yolo:
|
||||
results = list(out_dict.values())[0][0]
|
||||
objs = yolo.parse_yolov9(results)
|
||||
objs = yolo.parse_yolov8(results)
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
|
||||
@@ -267,12 +187,17 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
|
||||
out_dict = await asyncio.get_event_loop().run_in_executor(
|
||||
predictExecutor,
|
||||
lambda: self.model.predict(
|
||||
if asyncio.get_event_loop() is self.loop:
|
||||
out_dict = await asyncio.get_event_loop().run_in_executor(
|
||||
predictExecutor,
|
||||
lambda: self.model.predict(
|
||||
{"image": input, "confidenceThreshold": self.minThreshold}
|
||||
),
|
||||
)
|
||||
else:
|
||||
out_dict = self.model.predict(
|
||||
{"image": input, "confidenceThreshold": self.minThreshold}
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
coordinatesList = out_dict["coordinates"].astype(float)
|
||||
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import os
|
||||
|
||||
import asyncio
|
||||
import coremltools as ct
|
||||
import numpy as np
|
||||
# import Quartz
|
||||
# from Foundation import NSData, NSMakeSize
|
||||
|
||||
# import Vision
|
||||
from predict.face_recognize import FaceRecognizeDetection
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def euclidean_distance(arr1, arr2):
|
||||
return np.linalg.norm(arr1 - arr2)
|
||||
|
||||
|
||||
def cosine_similarity(vector_a, vector_b):
|
||||
dot_product = np.dot(vector_a, vector_b)
|
||||
norm_a = np.linalg.norm(vector_a)
|
||||
norm_b = np.linalg.norm(vector_b)
|
||||
similarity = dot_product / (norm_a * norm_b)
|
||||
return similarity
|
||||
|
||||
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(8, "Vision-Predict")
|
||||
|
||||
class CoreMLFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-face")
|
||||
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-face")
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
model_version = "v7"
|
||||
mlmodel = "model"
|
||||
|
||||
files = [
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{mlmodel}.mlmodel",
|
||||
f"{model}/{model}.mlpackage/Manifest.json",
|
||||
]
|
||||
|
||||
for f in files:
|
||||
p = self.downloadFile(
|
||||
f"https://github.com/koush/coreml-models/raw/main/{f}",
|
||||
f"{model_version}/{f}",
|
||||
)
|
||||
modelFile = os.path.dirname(p)
|
||||
|
||||
model = ct.models.MLModel(modelFile)
|
||||
inputName = model.get_spec().description.input[0].name
|
||||
return model, inputName
|
||||
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
def predict():
|
||||
model, inputName = self.detectModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
results = list(out_dict.values())[0][0]
|
||||
return results
|
||||
|
||||
results = await asyncio.get_event_loop().run_in_executor(
|
||||
self.detectExecutor, lambda: predict()
|
||||
)
|
||||
return results
|
||||
|
||||
async def predictFaceModel(self, input: np.ndarray):
|
||||
def predict():
|
||||
model, inputName = self.faceModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
results = list(out_dict.values())[0][0]
|
||||
return results
|
||||
results = await asyncio.get_event_loop().run_in_executor(
|
||||
self.recogExecutor, lambda: predict()
|
||||
)
|
||||
return results
|
||||
|
||||
# def predictVision(self, input: Image.Image) -> asyncio.Future[list[Prediction]]:
|
||||
# buffer = input.tobytes()
|
||||
# myData = NSData.alloc().initWithBytes_length_(buffer, len(buffer))
|
||||
|
||||
# input_image = (
|
||||
# Quartz.CIImage.imageWithBitmapData_bytesPerRow_size_format_options_(
|
||||
# myData,
|
||||
# 4 * input.width,
|
||||
# NSMakeSize(input.width, input.height),
|
||||
# Quartz.kCIFormatRGBA8,
|
||||
# None,
|
||||
# )
|
||||
# )
|
||||
|
||||
# request_handler = Vision.VNImageRequestHandler.alloc().initWithCIImage_options_(
|
||||
# input_image, None
|
||||
# )
|
||||
|
||||
# loop = self.loop
|
||||
# future = loop.create_future()
|
||||
|
||||
# def detect_face_handler(request, error):
|
||||
# observations = request.results()
|
||||
# if error:
|
||||
# loop.call_soon_threadsafe(future.set_exception, Exception())
|
||||
# else:
|
||||
# objs = []
|
||||
# for o in observations:
|
||||
# confidence = o.confidence()
|
||||
# bb = o.boundingBox()
|
||||
# origin = bb.origin
|
||||
# size = bb.size
|
||||
|
||||
# l = origin.x * input.width
|
||||
# t = (1 - origin.y - size.height) * input.height
|
||||
# w = size.width * input.width
|
||||
# h = size.height * input.height
|
||||
# prediction = Prediction(
|
||||
# 0, confidence, from_bounding_box((l, t, w, h))
|
||||
# )
|
||||
# objs.append(prediction)
|
||||
|
||||
# loop.call_soon_threadsafe(future.set_result, objs)
|
||||
|
||||
# request = (
|
||||
# Vision.VNDetectFaceRectanglesRequest.alloc().initWithCompletionHandler_(
|
||||
# detect_face_handler
|
||||
# )
|
||||
# )
|
||||
|
||||
# error = request_handler.performRequests_error_([request], None)
|
||||
# return future
|
||||
|
||||
# async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
# future = await asyncio.get_event_loop().run_in_executor(
|
||||
# predictExecutor,
|
||||
# lambda: self.predictVision(input),
|
||||
# )
|
||||
|
||||
# objs = await future
|
||||
# ret = self.create_detection_result(objs, src_size, cvss)
|
||||
# return ret
|
||||
@@ -1,63 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import os
|
||||
|
||||
import asyncio
|
||||
|
||||
import coremltools as ct
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from predict.text_recognize import TextRecognition
|
||||
|
||||
|
||||
class CoreMLTextRecognition(TextRecognition):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-text")
|
||||
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-text")
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
model_version = "v8"
|
||||
mlmodel = "model"
|
||||
|
||||
files = [
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{mlmodel}.mlmodel",
|
||||
f"{model}/{model}.mlpackage/Manifest.json",
|
||||
]
|
||||
|
||||
for f in files:
|
||||
p = self.downloadFile(
|
||||
f"https://github.com/koush/coreml-models/raw/main/{f}",
|
||||
f"{model_version}/{f}",
|
||||
)
|
||||
modelFile = os.path.dirname(p)
|
||||
|
||||
model = ct.models.MLModel(modelFile)
|
||||
inputName = model.get_spec().description.input[0].name
|
||||
return model, inputName
|
||||
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
def predict():
|
||||
model, inputName = self.detectModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
results = list(out_dict.values())[0]
|
||||
return results
|
||||
results = await asyncio.get_event_loop().run_in_executor(
|
||||
self.detectExecutor, lambda: predict()
|
||||
)
|
||||
return results
|
||||
|
||||
async def predictTextModel(self, input: np.ndarray):
|
||||
def predict():
|
||||
model, inputName = self.textModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
preds = out_dict["linear_2"]
|
||||
return preds
|
||||
preds = await asyncio.get_event_loop().run_in_executor(
|
||||
self.recogExecutor, lambda: predict()
|
||||
)
|
||||
return preds
|
||||
@@ -1 +1 @@
|
||||
../../openvino/src/detect/
|
||||
../../tensorflow-lite/src/detect
|
||||
@@ -1 +1 @@
|
||||
../../openvino/src/predict
|
||||
../../tensorflow-lite/src/predict
|
||||
@@ -1 +0,0 @@
|
||||
opencv-python==4.10.0.82
|
||||
@@ -1,4 +1,6 @@
|
||||
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
|
||||
numpy==1.26.4
|
||||
#
|
||||
coremltools==7.1
|
||||
Pillow==10.3.0
|
||||
|
||||
# pillow for anything not intel linux, pillow-simd is available on x64 linux
|
||||
Pillow>=5.4.1; sys_platform != 'linux' or platform_machine != 'x86_64'
|
||||
pillow-simd; sys_platform == 'linux' and platform_machine == 'x86_64'
|
||||
|
||||
1
plugins/coreml/src/yolo
Symbolic link
1
plugins/coreml/src/yolo
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openvino/src/yolo
|
||||
@@ -9,7 +9,4 @@ dist/*.js
|
||||
dist/*.txt
|
||||
__pycache__
|
||||
all_models
|
||||
sort_oh
|
||||
download_models.sh
|
||||
tsconfig.json
|
||||
.venv
|
||||
19
plugins/dlib/.vscode/settings.json
vendored
Normal file
19
plugins/dlib/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "koushik-thin",
|
||||
// "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.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
}
|
||||
3
plugins/dlib/README.md
Normal file
3
plugins/dlib/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Dlib Face Recognition for Scrypted
|
||||
|
||||
This plugin adds face recognition capabilities to any camera in Scrypted.
|
||||
BIN
plugins/dlib/fs/black.jpg
Normal file
BIN
plugins/dlib/fs/black.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -1,48 +1,47 @@
|
||||
{
|
||||
"name": "@scrypted/rknn",
|
||||
"version": "0.1.2",
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.0.18",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/rknn",
|
||||
"version": "0.1.2",
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.0.18",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.31",
|
||||
"version": "0.2.39",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
"scrypted-deploy": "bin/scrypted-deploy.js",
|
||||
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
|
||||
"scrypted-package-json": "bin/scrypted-package-json.js",
|
||||
"scrypted-readme": "bin/scrypted-readme.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
@@ -61,12 +60,12 @@
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
@@ -74,11 +73,10 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
{
|
||||
"name": "@scrypted/rknn",
|
||||
"description": "Scrypted Rockchip NPU Object Detection",
|
||||
"name": "@scrypted/dlib",
|
||||
"description": "Scrypted Face Recognition",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"rknn",
|
||||
"rockchip",
|
||||
"npu",
|
||||
"motion",
|
||||
"object",
|
||||
"dlib",
|
||||
"face",
|
||||
"detect",
|
||||
"detection",
|
||||
"recognition",
|
||||
"people",
|
||||
"person"
|
||||
],
|
||||
@@ -28,23 +26,21 @@
|
||||
"scrypted-package-json": "scrypted-package-json"
|
||||
},
|
||||
"scrypted": {
|
||||
"name": "Rockchip NPU Object Detection",
|
||||
"name": "Dlib Face Recognition",
|
||||
"pluginDependencies": [
|
||||
"@scrypted/objectdetector"
|
||||
],
|
||||
"runtime": "python",
|
||||
"pythonVersion": {
|
||||
"default": "3.10"
|
||||
},
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview",
|
||||
"DeviceProvider"
|
||||
"Camera",
|
||||
"Settings",
|
||||
"BufferConverter",
|
||||
"ObjectDetection"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.2"
|
||||
"version": "0.0.1"
|
||||
}
|
||||
1
plugins/dlib/src/detect
Symbolic link
1
plugins/dlib/src/detect
Symbolic link
@@ -0,0 +1 @@
|
||||
../../tensorflow-lite/src/detect
|
||||
252
plugins/dlib/src/dlibplugin/__init__.py
Normal file
252
plugins/dlib/src/dlibplugin/__init__.py
Normal file
@@ -0,0 +1,252 @@
|
||||
from __future__ import annotations
|
||||
import re
|
||||
import scrypted_sdk
|
||||
from typing import Any, Tuple
|
||||
from predict import PredictPlugin, Prediction, Rectangle
|
||||
import os
|
||||
from PIL import Image
|
||||
import face_recognition
|
||||
import numpy as np
|
||||
from typing import Any, List, Tuple, Mapping
|
||||
from scrypted_sdk.types import ObjectDetectionModel, ObjectDetectionResult, ObjectsDetected, Setting
|
||||
from predict import PredictSession
|
||||
import threading
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
from scrypted_sdk import RequestPictureOptions, MediaObject, Setting
|
||||
import os
|
||||
import json
|
||||
|
||||
def random_string():
|
||||
letters = string.ascii_lowercase
|
||||
return ''.join(random.choice(letters) for i in range(10))
|
||||
|
||||
|
||||
MIME_TYPE = 'x-scrypted-dlib/x-raw-image'
|
||||
|
||||
class DlibPlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(MIME_TYPE, nativeId=nativeId)
|
||||
|
||||
self.labels = {
|
||||
0: 'face'
|
||||
}
|
||||
|
||||
self.mutex = threading.Lock()
|
||||
self.known_faces = {}
|
||||
self.encoded_faces = {}
|
||||
self.load_known_faces()
|
||||
|
||||
def save_known_faces(self):
|
||||
j = json.dumps(self.known_faces)
|
||||
self.storage.setItem('known', j)
|
||||
|
||||
def load_known_faces(self):
|
||||
self.known_faces = {}
|
||||
self.encoded_faces = {}
|
||||
|
||||
try:
|
||||
self.known_faces = json.loads(self.storage.getItem('known'))
|
||||
except:
|
||||
pass
|
||||
|
||||
for known in self.known_faces:
|
||||
encoded = []
|
||||
self.encoded_faces[known] = encoded
|
||||
encodings = self.known_faces[known]
|
||||
for str in encodings:
|
||||
try:
|
||||
parsed = base64.decodebytes(bytes(str, 'utf-8'))
|
||||
encoding = np.frombuffer(parsed, dtype=np.float64)
|
||||
encoded.append(encoding)
|
||||
except:
|
||||
pass
|
||||
|
||||
# width, height, channels
|
||||
def get_input_details(self) -> Tuple[int, int, int]:
|
||||
pass
|
||||
|
||||
def get_input_size(self) -> Tuple[float, float]:
|
||||
pass
|
||||
|
||||
def getTriggerClasses(self) -> list[str]:
|
||||
return ['person']
|
||||
|
||||
def detect_once(self, input: Image.Image, settings: Any, src_size, cvss) -> ObjectsDetected:
|
||||
nparray = np.array(input.resize((int(input.width / 4), int(input.height / 4))))
|
||||
|
||||
with self.mutex:
|
||||
face_locations = face_recognition.face_locations(nparray)
|
||||
|
||||
for idx, face in enumerate(face_locations):
|
||||
t, r, b, l = face
|
||||
t *= 4
|
||||
r *= 4
|
||||
b *= 4
|
||||
l *= 4
|
||||
face_locations[idx] = (t, r, b, l)
|
||||
|
||||
nparray = np.array(input)
|
||||
|
||||
with self.mutex:
|
||||
face_encodings = face_recognition.face_encodings(nparray, face_locations)
|
||||
|
||||
all_ids = []
|
||||
all_faces = []
|
||||
for encoded in self.encoded_faces:
|
||||
all_ids += ([encoded] * len(self.encoded_faces[encoded]))
|
||||
all_faces += self.encoded_faces[encoded]
|
||||
|
||||
m = {}
|
||||
for idx, fe in enumerate(face_encodings):
|
||||
results = list(face_recognition.face_distance(all_faces, fe))
|
||||
|
||||
best = 1
|
||||
if len(results):
|
||||
best = min(results)
|
||||
minpos = results.index(best)
|
||||
|
||||
if best > .6:
|
||||
id = random_string() + '.jpg'
|
||||
print('top face %s' % best)
|
||||
print('new face %s' % id)
|
||||
encoded = [fe]
|
||||
self.encoded_faces[id] = encoded
|
||||
all_faces += encoded
|
||||
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
t, r, b, l = face_locations[idx]
|
||||
cropped = input.crop((l, t, r, b))
|
||||
fp = os.path.join(people, id)
|
||||
cropped.save(fp)
|
||||
else:
|
||||
id = all_ids[minpos]
|
||||
print('has face %s' % id)
|
||||
m[idx] = id
|
||||
|
||||
# return
|
||||
|
||||
objs = []
|
||||
|
||||
for face in face_locations:
|
||||
t, r, b, l = face
|
||||
obj = Prediction(0, 1, Rectangle(
|
||||
l,
|
||||
t,
|
||||
r,
|
||||
b
|
||||
))
|
||||
objs.append(obj)
|
||||
|
||||
ret = self.create_detection_result(objs, src_size, ['face'], cvss)
|
||||
|
||||
for idx, d in enumerate(ret['detections']):
|
||||
d['id'] = m.get(idx)
|
||||
d['name'] = m.get(idx)
|
||||
|
||||
return ret
|
||||
|
||||
def track(self, detection_session: PredictSession, ret: ObjectsDetected):
|
||||
pass
|
||||
|
||||
|
||||
async def takePicture(self, options: RequestPictureOptions = None) -> MediaObject:
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
for unknown in os.listdir(people):
|
||||
fp = os.path.join(people, unknown)
|
||||
ret = scrypted_sdk.mediaManager.createMediaObjectFromUrl('file:/' + fp)
|
||||
return await ret
|
||||
|
||||
black = os.path.join(volume, 'zip', 'unzipped', 'fs', 'black.jpg')
|
||||
ret = scrypted_sdk.mediaManager.createMediaObjectFromUrl('file:/' + black)
|
||||
return await ret
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
ret = []
|
||||
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
|
||||
choices = list(self.known_faces.keys())
|
||||
|
||||
for unknown in os.listdir(people):
|
||||
ret.append(
|
||||
{
|
||||
'key': unknown,
|
||||
'title': 'Name',
|
||||
'description': 'Associate this thumbnail with an existing person or identify a new person.',
|
||||
'choices': choices,
|
||||
'combobox': True,
|
||||
}
|
||||
)
|
||||
ret.append(
|
||||
{
|
||||
'key': 'delete',
|
||||
'title': 'Delete',
|
||||
'description': 'Delete this face.',
|
||||
'type': 'button',
|
||||
}
|
||||
)
|
||||
break
|
||||
|
||||
if not len(ret):
|
||||
ret.append(
|
||||
{
|
||||
'key': 'unknown',
|
||||
'title': 'Unknown People',
|
||||
'value': 'Waiting for unknown person...',
|
||||
'description': 'There are no more people that need to be identified.',
|
||||
'readonly': True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
ret.append(
|
||||
{
|
||||
'key': 'known',
|
||||
'group': 'People',
|
||||
'title': 'Familiar People',
|
||||
'description': 'The people known to this plugin.',
|
||||
'choices': choices,
|
||||
'multiple': True,
|
||||
'value': choices,
|
||||
}
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
async def putSetting(self, key: str, value: str) -> None:
|
||||
if key == 'known':
|
||||
n = {}
|
||||
for k in value:
|
||||
n[k] = self.known_faces[k]
|
||||
self.known_faces = n
|
||||
self.save_known_faces()
|
||||
elif value or key == 'delete':
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
for unknown in os.listdir(people):
|
||||
fp = os.path.join(people, unknown)
|
||||
os.remove(fp)
|
||||
if key != 'delete':
|
||||
encoded = self.encoded_faces[key]
|
||||
strs = []
|
||||
for e in encoded:
|
||||
strs.append(base64.encodebytes(e.tobytes()).decode())
|
||||
if not self.known_faces.get(value):
|
||||
self.known_faces[value] = []
|
||||
self.known_faces[value] += strs
|
||||
self.save_known_faces()
|
||||
break
|
||||
|
||||
await self.onDeviceEvent(scrypted_sdk.ScryptedInterface.Settings.value, None)
|
||||
await self.onDeviceEvent(scrypted_sdk.ScryptedInterface.Camera.value, None)
|
||||
4
plugins/dlib/src/main.py
Normal file
4
plugins/dlib/src/main.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from dlibplugin import DlibPlugin
|
||||
|
||||
def create_scrypted_plugin():
|
||||
return DlibPlugin()
|
||||
1
plugins/dlib/src/pipeline
Symbolic link
1
plugins/dlib/src/pipeline
Symbolic link
@@ -0,0 +1 @@
|
||||
../../tensorflow-lite/src/pipeline
|
||||
1
plugins/dlib/src/predict
Symbolic link
1
plugins/dlib/src/predict
Symbolic link
@@ -0,0 +1 @@
|
||||
../../tensorflow-lite/src/predict
|
||||
10
plugins/dlib/src/requirements.txt
Normal file
10
plugins/dlib/src/requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# plugin
|
||||
Pillow>=5.4.1
|
||||
PyGObject>=3.30.4; sys_platform != 'win32'
|
||||
av>=10.0.0; sys_platform != 'linux' or platform_machine == 'x86_64' or platform_machine == 'aarch64'
|
||||
face-recognition
|
||||
|
||||
# sort_oh
|
||||
scipy
|
||||
filterpy
|
||||
numpy
|
||||
104
plugins/hikvision/package-lock.json
generated
104
plugins/hikvision/package-lock.json
generated
@@ -1,23 +1,22 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.149",
|
||||
"version": "0.0.137",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.149",
|
||||
"version": "0.0.137",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"content-type": "^1.0.5",
|
||||
"xml2js": "^0.6.2"
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"lodash": "^4.17.21",
|
||||
"xml2js": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
"@types/node": "^18.15.11"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -28,16 +27,17 @@
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/node": "^20.10.8",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.29",
|
||||
"version": "0.3.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -83,50 +83,33 @@
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/content-type": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.8.tgz",
|
||||
"integrity": "sha512-1tBhmVUeso3+ahfyaKluXe38p+94lovUZdoVfQ3OnJo9uJC42JT7CBoN3k9HYhAae+GwiBYmHu+N9FZhOG+2Pg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
|
||||
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
"version": "18.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||
},
|
||||
"node_modules/@types/xml2js": {
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.11.tgz",
|
||||
"integrity": "sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
|
||||
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
@@ -150,8 +133,9 @@
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/node": "^20.10.8",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
@@ -180,47 +164,33 @@
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"@types/content-type": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.8.tgz",
|
||||
"integrity": "sha512-1tBhmVUeso3+ahfyaKluXe38p+94lovUZdoVfQ3OnJo9uJC42JT7CBoN3k9HYhAae+GwiBYmHu+N9FZhOG+2Pg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "20.11.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
|
||||
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
"version": "18.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||
},
|
||||
"@types/xml2js": {
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.11.tgz",
|
||||
"integrity": "sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
|
||||
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.149",
|
||||
"version": "0.0.137",
|
||||
"description": "Hikvision Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -37,12 +37,11 @@
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"content-type": "^1.0.5",
|
||||
"xml2js": "^0.6.2"
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"lodash": "^4.17.21",
|
||||
"xml2js": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
"@types/node": "^18.15.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import { AuthFetchCredentialState, HttpFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { readLine } from '@scrypted/common/src/read-stream';
|
||||
import { parseHeaders, readBody, readMessage } from '@scrypted/common/src/rtsp-server';
|
||||
import contentType from 'content-type';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { EventEmitter, Readable } from 'stream';
|
||||
import { Destroyable } from '../../rtsp/src/rtsp';
|
||||
import { Readable } from 'stream';
|
||||
import { getDeviceInfo } from './probe';
|
||||
|
||||
export const detectionMap = {
|
||||
human: 'person',
|
||||
vehicle: 'car',
|
||||
}
|
||||
|
||||
export function getChannel(channel: string) {
|
||||
return channel || '101';
|
||||
}
|
||||
@@ -24,8 +15,6 @@ export enum HikvisionCameraEvent {
|
||||
// <eventType>linedetection</eventType>
|
||||
// <eventState>inactive</eventState>
|
||||
LineDetection = "<eventType>linedetection</eventType>",
|
||||
RegionEntrance = "<eventType>regionEntrance</eventType>",
|
||||
RegionExit = "<eventType>regionExit</eventType>",
|
||||
// <eventType>fielddetection</eventType>
|
||||
// <eventState>active</eventState>
|
||||
// <eventType>fielddetection</eventType>
|
||||
@@ -42,7 +31,7 @@ export interface HikvisionCameraStreamSetup {
|
||||
export class HikvisionCameraAPI {
|
||||
credential: AuthFetchCredentialState;
|
||||
deviceModel: Promise<string>;
|
||||
listenerPromise: Promise<Destroyable>;
|
||||
listenerPromise: Promise<IncomingMessage>;
|
||||
|
||||
constructor(public ip: string, username: string, password: string, public console: Console) {
|
||||
this.credential = {
|
||||
@@ -140,108 +129,35 @@ export class HikvisionCameraAPI {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async listenEvents(): Promise<Destroyable> {
|
||||
const events = new EventEmitter();
|
||||
(events as any).destroy = () => { };
|
||||
async listenEvents() {
|
||||
// support multiple cameras listening to a single single stream
|
||||
if (!this.listenerPromise) {
|
||||
const url = `http://${this.ip}/ISAPI/Event/notification/alertStream`;
|
||||
|
||||
|
||||
let lastSmartDetection: string;
|
||||
|
||||
this.listenerPromise = this.request({
|
||||
url,
|
||||
responseType: 'readable',
|
||||
}).then(response => {
|
||||
const stream: IncomingMessage = response.body;
|
||||
(events as any).destroy = () => {
|
||||
stream.destroy();
|
||||
events.removeAllListeners();
|
||||
};
|
||||
stream.on('close', () => {
|
||||
this.listenerPromise = undefined;
|
||||
events.emit('close');
|
||||
});
|
||||
stream.on('end', () => {
|
||||
this.listenerPromise = undefined;
|
||||
events.emit('end');
|
||||
});
|
||||
stream.on('error', e => {
|
||||
this.listenerPromise = undefined;
|
||||
events.emit('error', e);
|
||||
});
|
||||
const stream = response.body;
|
||||
stream.socket.setKeepAlive(true);
|
||||
|
||||
const ct = stream.headers['content-type'];
|
||||
// make content type parsable as content disposition filename
|
||||
const cd = contentType.parse(ct);
|
||||
let { boundary } = cd.parameters;
|
||||
boundary = `--${boundary}`;
|
||||
const boundaryEnd = `${boundary}--`;
|
||||
|
||||
|
||||
(async () => {
|
||||
while (true) {
|
||||
let ignore = await readLine(stream);
|
||||
ignore = ignore.trim();
|
||||
if (!ignore)
|
||||
continue;
|
||||
if (ignore === boundaryEnd)
|
||||
continue;
|
||||
if (ignore !== boundary
|
||||
// older hikvision nvr send a boundary in the headers, but then use a totally different constant boundary value
|
||||
&& ignore != "--boundary") {
|
||||
this.console.error('expected boundary but found', ignore);
|
||||
throw new Error('expected boundary');
|
||||
}
|
||||
|
||||
const message = await readMessage(stream);
|
||||
events.emit('data', message);
|
||||
message.unshift('');
|
||||
const headers = parseHeaders(message);
|
||||
const body = await readBody(stream, headers);
|
||||
|
||||
try {
|
||||
if (!headers['content-type'].includes('application/xml') && lastSmartDetection) {
|
||||
if (!headers['content-type']?.startsWith('image/jpeg')) {
|
||||
continue;
|
||||
}
|
||||
events.emit('smart', lastSmartDetection, body);
|
||||
lastSmartDetection = undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
// is it possible that smart detections are sent without images?
|
||||
// if so, flush this detection.
|
||||
if (lastSmartDetection) {
|
||||
events.emit('smart', lastSmartDetection);
|
||||
}
|
||||
}
|
||||
|
||||
const data = body.toString();
|
||||
events.emit('data', data);
|
||||
for (const event of Object.values(HikvisionCameraEvent)) {
|
||||
if (data.indexOf(event) !== -1) {
|
||||
const cameraNumber = data.match(/<channelID>(.*?)</)?.[1] || data.match(/<dynChannelID>(.*?)</)?.[1];
|
||||
const inactive = data.indexOf('<eventState>inactive</eventState>') !== -1;
|
||||
events.emit('event', event, cameraNumber, inactive, data);
|
||||
if (event === HikvisionCameraEvent.LineDetection
|
||||
|| event === HikvisionCameraEvent.RegionEntrance
|
||||
|| event === HikvisionCameraEvent.RegionExit
|
||||
|| event === HikvisionCameraEvent.FieldDetection) {
|
||||
lastSmartDetection = data;
|
||||
}
|
||||
}
|
||||
stream.on('data', (buffer: Buffer) => {
|
||||
const data = buffer.toString();
|
||||
for (const event of Object.values(HikvisionCameraEvent)) {
|
||||
if (data.indexOf(event) !== -1) {
|
||||
const cameraNumber = data.match(/<channelID>(.*?)</)?.[1] || data.match(/<dynChannelID>(.*?)</)?.[1];
|
||||
const inactive = data.indexOf('<eventState>inactive</eventState>') !== -1;
|
||||
stream.emit('event', event, cameraNumber, inactive, data);
|
||||
}
|
||||
}
|
||||
})()
|
||||
.catch(() => stream.destroy());
|
||||
return events as any as Destroyable;
|
||||
});
|
||||
return stream;
|
||||
});
|
||||
this.listenerPromise.catch(() => this.listenerPromise = undefined);
|
||||
this.listenerPromise.then(stream => {
|
||||
stream.on('close', () => this.listenerPromise = undefined);
|
||||
stream.on('end', () => this.listenerPromise = undefined);
|
||||
});
|
||||
}
|
||||
|
||||
return this.listenerPromise;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, ObjectDetectionResult, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, Reboot, RequestPictureOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting } from "@scrypted/sdk";
|
||||
import crypto from 'crypto';
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, Reboot, RequestPictureOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting } from "@scrypted/sdk";
|
||||
import { PassThrough } from "stream";
|
||||
import xml2js from 'xml2js';
|
||||
import { RtpPacket } from '../../../external/werift/packages/rtp/src/rtp/rtp';
|
||||
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, detectionMap } from "./hikvision-camera-api";
|
||||
import { HikvisionCameraAPI, HikvisionCameraEvent } from "./hikvision-camera-api";
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
@@ -16,17 +15,15 @@ function channelToCameraNumber(channel: string) {
|
||||
return channel.substring(0, channel.length - 2);
|
||||
}
|
||||
|
||||
class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboot, ObjectDetector {
|
||||
class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboot {
|
||||
detectedChannels: Promise<Map<string, MediaStreamOptions>>;
|
||||
client: HikvisionCameraAPI;
|
||||
onvifIntercom = new OnvifIntercom(this);
|
||||
activeIntercom: Awaited<ReturnType<typeof startRtpForwarderProcess>>;
|
||||
hasSmartDetection: boolean;
|
||||
|
||||
constructor(nativeId: string, provider: RtspProvider) {
|
||||
super(nativeId, provider);
|
||||
|
||||
this.hasSmartDetection = this.storage.getItem('hasSmartDetection') === 'true';
|
||||
this.updateDevice();
|
||||
this.updateDeviceInfo();
|
||||
}
|
||||
@@ -66,52 +63,41 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
let ignoreCameraNumber: boolean;
|
||||
|
||||
const motionTimeoutDuration = 20000;
|
||||
|
||||
// check if the camera+channel field is in use, and filter events.
|
||||
const checkCameraNumber = async (cameraNumber: string) => {
|
||||
// check if the camera+channel field is in use, and filter events.
|
||||
if (this.getRtspChannel()) {
|
||||
// it is possible to set it up to use a camera number
|
||||
// on an nvr IP (which gives RTSP urls through the NVR), but then use a http port
|
||||
// that gives a filtered event stream from only that camera.
|
||||
// this this case, the camera numbers will not
|
||||
// match as they will be always be "1".
|
||||
// to detect that a camera specific endpoint is being used
|
||||
// can look at the channel ids, and see if that camera number is found.
|
||||
// this is different from the use case where the NVR or camera
|
||||
// is using a port other than 80 (the default).
|
||||
// could add a setting to have the user explicitly denote nvr usage
|
||||
// but that is error prone.
|
||||
const userCameraNumber = this.getCameraNumber();
|
||||
if (ignoreCameraNumber === undefined && this.detectedChannels) {
|
||||
const channelIds = (await this.detectedChannels).keys();
|
||||
ignoreCameraNumber = true;
|
||||
for (const id of channelIds) {
|
||||
if (channelToCameraNumber(id) === userCameraNumber) {
|
||||
ignoreCameraNumber = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignoreCameraNumber && cameraNumber !== userCameraNumber) {
|
||||
// this.console.error(`### Skipping motion event ${cameraNumber} != ${this.getCameraNumber()}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
events.on('event', async (event: HikvisionCameraEvent, cameraNumber: string, inactive: boolean) => {
|
||||
if (event === HikvisionCameraEvent.MotionDetected
|
||||
|| event === HikvisionCameraEvent.LineDetection
|
||||
|| event === HikvisionCameraEvent.RegionEntrance
|
||||
|| event === HikvisionCameraEvent.RegionExit
|
||||
|| event === HikvisionCameraEvent.FieldDetection) {
|
||||
|
||||
if (!await checkCameraNumber(cameraNumber))
|
||||
return;
|
||||
// check if the camera+channel field is in use, and filter events.
|
||||
if (this.getRtspChannel()) {
|
||||
// it is possible to set it up to use a camera number
|
||||
// on an nvr IP (which gives RTSP urls through the NVR), but then use a http port
|
||||
// that gives a filtered event stream from only that camera.
|
||||
// this this case, the camera numbers will not
|
||||
// match as they will be always be "1".
|
||||
// to detect that a camera specific endpoint is being used
|
||||
// can look at the channel ids, and see if that camera number is found.
|
||||
// this is different from the use case where the NVR or camera
|
||||
// is using a port other than 80 (the default).
|
||||
// could add a setting to have the user explicitly denote nvr usage
|
||||
// but that is error prone.
|
||||
const userCameraNumber = this.getCameraNumber();
|
||||
if (ignoreCameraNumber === undefined && this.detectedChannels) {
|
||||
const channelIds = (await this.detectedChannels).keys();
|
||||
ignoreCameraNumber = true;
|
||||
for (const id of channelIds) {
|
||||
if (channelToCameraNumber(id) === userCameraNumber) {
|
||||
ignoreCameraNumber = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignoreCameraNumber && cameraNumber !== userCameraNumber) {
|
||||
// this.console.error(`### Skipping motion event ${cameraNumber} != ${this.getCameraNumber()}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.motionDetected = true;
|
||||
clearTimeout(motionTimeout);
|
||||
@@ -120,107 +106,11 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
this.motionDetected = false;
|
||||
}, motionTimeoutDuration);
|
||||
}
|
||||
});
|
||||
|
||||
let inputDimensions: [number, number];
|
||||
|
||||
events.on('smart', async (data: string, image: Buffer) => {
|
||||
if (!this.hasSmartDetection) {
|
||||
this.hasSmartDetection = true;
|
||||
this.storage.setItem('hasSmartDetection', 'true');
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
const xml = await xml2js.parseStringPromise(data);
|
||||
|
||||
|
||||
const [channelId] = xml.EventNotificationAlert.channelID || xml.EventNotificationAlert.dynChannelID;
|
||||
if (!await checkCameraNumber(channelId)) {
|
||||
this.console.warn('chann fail')
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
let detections: ObjectDetectionResult[] = xml.EventNotificationAlert?.DetectionRegionList?.map(region => {
|
||||
const { DetectionRegionEntry } = region;
|
||||
const dre = DetectionRegionEntry[0];
|
||||
if (!DetectionRegionEntry)
|
||||
return;
|
||||
const { detectionTarget } = dre;
|
||||
// const { TargetRect } = dre;
|
||||
// const { X, Y, width, height } = TargetRect[0];
|
||||
const [name] = detectionTarget;
|
||||
return {
|
||||
score: 1,
|
||||
className: detectionMap[name] || name,
|
||||
// boundingBox: [
|
||||
// parseInt(X),
|
||||
// parseInt(Y),
|
||||
// parseInt(width),
|
||||
// parseInt(height),
|
||||
// ],
|
||||
// movement: {
|
||||
// moving: true,
|
||||
// firstSeen: now,
|
||||
// lastSeen: now,
|
||||
// }
|
||||
} as ObjectDetectionResult;
|
||||
});
|
||||
|
||||
detections = detections?.filter(d => d);
|
||||
if (!detections?.length)
|
||||
return;
|
||||
|
||||
// if (inputDimensions === undefined && loadSharp()) {
|
||||
// try {
|
||||
// const { image: i, metadata } = await loadVipsMetadata(image);
|
||||
// i.destroy();
|
||||
// inputDimensions = [metadata.width, metadata.height];
|
||||
// }
|
||||
// catch (e) {
|
||||
// inputDimensions = null;
|
||||
// }
|
||||
// finally {
|
||||
// }
|
||||
// }
|
||||
|
||||
let detectionId: string;
|
||||
if (image) {
|
||||
detectionId = crypto.randomBytes(4).toString('hex');
|
||||
this.recentDetections.set(detectionId, image);
|
||||
setTimeout(() => this.recentDetections.delete(detectionId), 10000);
|
||||
}
|
||||
|
||||
const detected: ObjectsDetected = {
|
||||
inputDimensions,
|
||||
detectionId,
|
||||
timestamp: now,
|
||||
detections,
|
||||
};
|
||||
|
||||
this.onDeviceEvent(ScryptedInterface.ObjectDetector, detected);
|
||||
});
|
||||
})
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
recentDetections = new Map<string, Buffer>();
|
||||
|
||||
async getDetectionInput(detectionId: string, eventId?: any): Promise<MediaObject> {
|
||||
const image = this.recentDetections.get(detectionId);
|
||||
if (!image)
|
||||
return;
|
||||
return mediaManager.createMediaObject(image, 'image/jpeg');
|
||||
}
|
||||
|
||||
async getObjectTypes(): Promise<ObjectDetectionTypes> {
|
||||
return {
|
||||
classes: [
|
||||
...Object.values(detectionMap),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
createClient() {
|
||||
return new HikvisionCameraAPI(this.getHttpAddress(), this.getUsername(), this.getPassword(), this.console);
|
||||
}
|
||||
@@ -394,9 +284,6 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
interfaces.push(ScryptedInterface.Intercom);
|
||||
}
|
||||
|
||||
if (this.hasSmartDetection)
|
||||
interfaces.push(ScryptedInterface.ObjectDetector);
|
||||
|
||||
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
|
||||
}
|
||||
|
||||
@@ -521,7 +408,7 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
const put = this.getClient().request({
|
||||
url,
|
||||
method: 'PUT',
|
||||
responseType: 'text',
|
||||
responseType: 'readable',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
// 'Connection': 'close',
|
||||
@@ -553,12 +440,6 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
forwarder.killPromise.finally(() => {
|
||||
this.console.log('audio finished');
|
||||
passthrough.end();
|
||||
setTimeout(() => {
|
||||
this.stopIntercom();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
put.finally(() => {
|
||||
this.stopIntercom();
|
||||
});
|
||||
|
||||
@@ -567,7 +448,7 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboo
|
||||
if (response.statusCode !== 200)
|
||||
forwarder.kill();
|
||||
})
|
||||
.catch(() => forwarder.kill());
|
||||
.catch(() => forwarder.kill());
|
||||
}
|
||||
|
||||
async stopIntercom(): Promise<void> {
|
||||
@@ -700,4 +581,4 @@ class HikvisionProvider extends RtspProvider {
|
||||
}
|
||||
}
|
||||
|
||||
export default HikvisionProvider;
|
||||
export default new HikvisionProvider();
|
||||
|
||||
2
plugins/homekit/.vscode/settings.json
vendored
2
plugins/homekit/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1"
|
||||
"scrypted.debugHost": "scrypted-server"
|
||||
}
|
||||
@@ -32,13 +32,10 @@ If recordings dont work, it's generally because of a few reasons, **follow the s
|
||||
|
||||
### HomeKit Discovery and Pairing Issues
|
||||
|
||||
* Ensure all your Apple TV and Home Pods are online and updated. Power cycling them is recommended in case one is stuck.
|
||||
* Ensure your Apple TV and Home Pods are on the same subnet as the Scrypted server.
|
||||
* Ensure all your Home hubs are online and updated. Power cycling them is recommended in case one is stuck.
|
||||
* Ensure LAN/WLAN multicast is enabled on your router.
|
||||
* Ensure the iOS device you are using for pairing is on the same network (pairing will fail on cellular).
|
||||
* Ensure the Docker installation (if applicable) is using host networking. This configuration is the default if the official Scrypted Docker compose install script was used.
|
||||
* Try switching the mDNS advertiser used in the HomeKit plugin settings.
|
||||
* Try disabling IGMP Snooping on your router.
|
||||
|
||||
### HomeKit Live Streaming Timeout (Recordings may be working)
|
||||
|
||||
|
||||
38
plugins/homekit/package-lock.json
generated
38
plugins/homekit/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.57",
|
||||
"version": "1.2.43",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.57",
|
||||
"version": "1.2.43",
|
||||
"dependencies": {
|
||||
"@koush/werift-src": "file:../../external/werift",
|
||||
"check-disk-space": "^3.4.0",
|
||||
@@ -47,20 +47,26 @@
|
||||
"examples/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.4.1",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/node": "^20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"knip": "^3.9.0",
|
||||
"knip": "^3.7.0",
|
||||
"node-actionlint": "^1.2.2",
|
||||
"organize-imports-cli": "^0.10.0",
|
||||
"prettier": "^3.1.1",
|
||||
"process": "^0.11.10",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typedoc": "0.25.5",
|
||||
"typedoc": "0.25.4",
|
||||
"typedoc-plugin-markdown": "3.17.1",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
@@ -121,7 +127,7 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.29",
|
||||
"version": "0.3.18",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -1300,20 +1306,26 @@
|
||||
"@koush/werift-src": {
|
||||
"version": "file:../../external/werift",
|
||||
"requires": {
|
||||
"@biomejs/biome": "^1.4.1",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/node": "^20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"knip": "^3.9.0",
|
||||
"knip": "^3.7.0",
|
||||
"node-actionlint": "^1.2.2",
|
||||
"organize-imports-cli": "^0.10.0",
|
||||
"prettier": "^3.1.1",
|
||||
"process": "^0.11.10",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typedoc": "0.25.5",
|
||||
"typedoc": "0.25.4",
|
||||
"typedoc-plugin-markdown": "3.17.1",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.0.4"
|
||||
}
|
||||
},
|
||||
"@leichtgewicht/ip-codec": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.57",
|
||||
"version": "1.2.43",
|
||||
"description": "HomeKit Plugin for Scrypted",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
|
||||
@@ -66,7 +66,7 @@ export function createHAPUsername() {
|
||||
}
|
||||
|
||||
export function getAddresses() {
|
||||
const addresses = Object.entries(os.networkInterfaces()).filter(([iface]) => iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan') || iface.startsWith('net')).map(([_, addr]) => addr).flat().map(info => info.address).filter(address => address);
|
||||
const addresses = Object.entries(os.networkInterfaces()).filter(([iface]) => iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')).map(([_, addr]) => addr).flat().map(info => info.address).filter(address => address);
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@@ -74,17 +74,13 @@ export function getRandomPort() {
|
||||
return Math.round(30000 + Math.random() * 20000);
|
||||
}
|
||||
|
||||
export function createHAPUsernameStorageSettingsDict(device: { storage: Storage, name?: string }, group: string, subgroup?: string): StorageSettingsDict<'mac' | 'addIdentifyingMaterial' | 'qrCode' | 'pincode' | 'portOverride' | 'resetAccessory'> {
|
||||
export function createHAPUsernameStorageSettingsDict(device: { storage: Storage, name?: string }, group: string, subgroup?: string): StorageSettingsDict<'mac' | 'qrCode' | 'pincode' | 'portOverride' | 'resetAccessory'> {
|
||||
const alertReload = () => {
|
||||
sdk.log.a(`The HomeKit plugin will reload momentarily for the changes to ${device.name} to take effect.`);
|
||||
sdk.deviceManager.requestRestart();
|
||||
}
|
||||
|
||||
return {
|
||||
addIdentifyingMaterial: {
|
||||
hide: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
qrCode: {
|
||||
group,
|
||||
// subgroup,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user