This commit is contained in:
Koushik Dutta
2021-09-01 12:17:07 -07:00
parent 1b8467f66a
commit 20fdb8e7cb
10 changed files with 732 additions and 0 deletions

4
plugins/amcrest/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.DS_Store
out/
node_modules/
dist/

View File

@@ -0,0 +1,4 @@
.DS_Store
out/
node_modules/
dist/*.map

22
plugins/amcrest/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Scrypted Debugger",
"address": "${config:scrypted.debugHost}",
"port": 9091,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "pwa-node"
}
]
}

4
plugins/amcrest/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"scrypted.debugHost": "127.0.0.1",
}

20
plugins/amcrest/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "scrypted: deploy+debug",
"type": "shell",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
},
]
}

15
plugins/amcrest/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Amcrest Plugin
## npm commands
* npm run scrypted-webpack
* npm run scrypted-deploy <ipaddress>
* npm run scrypted-debug <ipaddress>
## scrypted distribution via npm
1. Ensure package.json is set up properly for publishing on npm.
2. npm publish
## Visual Studio Code configuration
* If using a remote server, edit [.vscode/settings.json](blob/master/.vscode/settings.json) to specify the IP Address of the Scrypted server.
* Launch Scrypted Debugger from the launch menu.

413
plugins/amcrest/package-lock.json generated Normal file
View File

@@ -0,0 +1,413 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.16",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.16",
"license": "Apache",
"dependencies": {
"@mhoc/axios-digest-auth": "^0.7.0",
"axios": "^0.21.1",
"multiparty": "^4.2.2",
"url-parse": "^1.4.7"
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk",
"@types/multiparty": "^0.0.33",
"@types/node": "^16.7.10"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.0.67",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
"@babel/plugin-transform-typescript": "^7.15.0",
"@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.2.3",
"@babel/preset-typescript": "^7.15.0",
"adm-zip": "^0.4.13",
"axios": "^0.21.1",
"babel-loader": "^8.0.4",
"babel-polyfill": "^6.26.0",
"babel-template": "^6.26.0",
"browserify-buffertools": "^1.0.2",
"bytebuffer": "^5.0.1",
"chalk": "^2.4.2",
"clean-webpack-plugin": "^3.0.0",
"engine.io-client": "^3.3.2",
"event-target-shim": "^5.0.1",
"events": "^3.0.0",
"long": "^4.0.0",
"node-cmd": "^3.0.0",
"node-ip": "^0.1.2",
"raw-loader": "^1.0.0",
"terser": "^3.14.1",
"ts-loader": "^5.4.5",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"typescript": "^4.3.5",
"webpack": "^4.28.1",
"webpack-cli": "^3.1.2",
"webpack-inject-plugin": "^1.0.2"
},
"bin": {
"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-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@types/node": "^16.6.1",
"babel-plugin-const-enum": "^1.1.0",
"babel-plugin-minify-dead-code-elimination": "^0.5.1"
}
},
"../sdk": {
"extraneous": true
},
"node_modules/@mhoc/axios-digest-auth": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@mhoc/axios-digest-auth/-/axios-digest-auth-0.7.0.tgz",
"integrity": "sha512-fDi8fNmuLmX86XEtqD6E59ytLROjmo3O9ZZAT5ndnFJpntOg1912sV/dA218GfQv6UviCFnpEogwuyWTnHmGng==",
"dependencies": {
"axios": "0.21.1"
}
},
"node_modules/@scrypted/sdk": {
"resolved": "../../sdk",
"link": true
},
"node_modules/@types/multiparty": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-0.0.33.tgz",
"integrity": "sha512-Il6cJUpSqgojT7NxbVJUvXkCblm50/yEJYtblISDsNIeNYf4yMAhdizzidUk6h8pJ8yhwK/3Fkb+3Dwcgtwl8w==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "16.7.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz",
"integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==",
"dev": true
},
"node_modules/axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"dependencies": {
"follow-redirects": "^1.10.0"
}
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/follow-redirects": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/http-errors": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz",
"integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==",
"dependencies": {
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/multiparty": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz",
"integrity": "sha512-NtZLjlvsjcoGrzojtwQwn/Tm90aWJ6XXtPppYF4WmOk/6ncdwMMKggFY2NlRRN9yiCEIVxpOfPWahVEG2HAG8Q==",
"dependencies": {
"http-errors": "~1.8.0",
"safe-buffer": "5.2.1",
"uid-safe": "2.1.5"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/querystringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/url-parse": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
}
},
"dependencies": {
"@mhoc/axios-digest-auth": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@mhoc/axios-digest-auth/-/axios-digest-auth-0.7.0.tgz",
"integrity": "sha512-fDi8fNmuLmX86XEtqD6E59ytLROjmo3O9ZZAT5ndnFJpntOg1912sV/dA218GfQv6UviCFnpEogwuyWTnHmGng==",
"requires": {
"axios": "0.21.1"
}
},
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
"@babel/plugin-transform-typescript": "^7.15.0",
"@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.2.3",
"@babel/preset-typescript": "^7.15.0",
"@types/node": "^16.6.1",
"adm-zip": "^0.4.13",
"axios": "^0.21.1",
"babel-loader": "^8.0.4",
"babel-plugin-const-enum": "^1.1.0",
"babel-plugin-minify-dead-code-elimination": "^0.5.1",
"babel-polyfill": "^6.26.0",
"babel-template": "^6.26.0",
"browserify-buffertools": "^1.0.2",
"bytebuffer": "^5.0.1",
"chalk": "^2.4.2",
"clean-webpack-plugin": "^3.0.0",
"engine.io-client": "^3.3.2",
"event-target-shim": "^5.0.1",
"events": "^3.0.0",
"long": "^4.0.0",
"node-cmd": "^3.0.0",
"node-ip": "^0.1.2",
"raw-loader": "^1.0.0",
"terser": "^3.14.1",
"ts-loader": "^5.4.5",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"typescript": "^4.3.5",
"webpack": "^4.28.1",
"webpack-cli": "^3.1.2",
"webpack-inject-plugin": "^1.0.2"
}
},
"@types/multiparty": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-0.0.33.tgz",
"integrity": "sha512-Il6cJUpSqgojT7NxbVJUvXkCblm50/yEJYtblISDsNIeNYf4yMAhdizzidUk6h8pJ8yhwK/3Fkb+3Dwcgtwl8w==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "16.7.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz",
"integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==",
"dev": true
},
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"follow-redirects": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA=="
},
"http-errors": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz",
"integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"multiparty": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz",
"integrity": "sha512-NtZLjlvsjcoGrzojtwQwn/Tm90aWJ6XXtPppYF4WmOk/6ncdwMMKggFY2NlRRN9yiCEIVxpOfPWahVEG2HAG8Q==",
"requires": {
"http-errors": "~1.8.0",
"safe-buffer": "5.2.1",
"uid-safe": "2.1.5"
}
},
"querystringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
},
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"requires": {
"random-bytes": "~1.0.0"
}
},
"url-parse": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
}
}
}

View File

@@ -0,0 +1,42 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.16",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",
"scripts": {
"prepublishOnly": "NODE_ENV=production scrypted-webpack",
"prescrypted-vscode-launch": "scrypted-webpack",
"scrypted-vscode-launch": "scrypted-deploy-debug",
"scrypted-deploy-debug": "scrypted-deploy-debug",
"scrypted-debug": "scrypted-debug",
"scrypted-deploy": "scrypted-deploy",
"scrypted-readme": "scrypted-readme",
"scrypted-package-json": "scrypted-package-json",
"scrypted-webpack": "scrypted-webpack"
},
"keywords": [
"scrypted",
"plugin",
"amcrest"
],
"scrypted": {
"name": "Amcrest Camera Controller",
"type": "DeviceProvider",
"interfaces": [
"DeviceProvider",
"Settings"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk",
"@types/multiparty": "^0.0.33",
"@types/node": "^16.7.10"
},
"dependencies": {
"@mhoc/axios-digest-auth": "^0.7.0",
"axios": "^0.21.1",
"multiparty": "^4.2.2",
"url-parse": "^1.4.7"
}
}

View File

@@ -0,0 +1,75 @@
import AxiosDigestAuth from '@mhoc/axios-digest-auth';
import { Socket } from 'net';
import { PassThrough, Readable, Stream } from 'stream';
import {Form, Part} from 'multiparty';
import { once } from 'events';
export enum AmcrestEvent {
MotionStart = "MotionStart",
MotionStop = "MotionStop",
}
async function readEvent(readable: Readable): Promise<AmcrestEvent|void> {
const pt = new PassThrough();
readable.pipe(pt);
const buffers: Buffer[] = [];
for await (const buffer of pt) {
buffers.push(buffer);
const data = Buffer.concat(buffers).toString();
if (data.indexOf('Code=VideoMotion;action=Stop') !== -1) {
return AmcrestEvent.MotionStop;
}
else if (data.indexOf('Code=VideoMotion;action=Start') !== -1) {
return AmcrestEvent.MotionStart;
}
}
}
export class AmcrestCameraClient {
digestAuth: AxiosDigestAuth;
constructor(public ip: string, public username: string, public password: string) {
this.digestAuth = new AxiosDigestAuth({
username,
password,
});
}
async jpegSnapshot(): Promise<Buffer> {
const response = await this.digestAuth.request({
method: "GET",
responseType: 'arraybuffer',
url: `http://${this.ip}/cgi-bin/snapshot.cgi`,
});
return Buffer.from(response.data);
}
async* listenForMotionEvents(): AsyncGenerator<AmcrestEvent> {
const response = await this.digestAuth.request({
method: "GET",
responseType: 'stream',
url: `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[VideoMotion]`,
});
const stream = response.data;
const form = new Form();
try {
// massage this so the parser doesn't fail on a bad content type
stream.headers['content-type'] = stream.headers['content-type'].replace('multipart/x-mixed-replace', 'multipart/form-data');
form.parse(stream);
while (true) {
const [part] = await once(form, 'part');
const event = await readEvent(part);
if (event)
yield event;
}
}
finally {
stream.destroy();
}
}
}

133
plugins/amcrest/src/main.ts Normal file
View File

@@ -0,0 +1,133 @@
import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, ScryptedDeviceType, VideoCamera, MediaObject, VideoStreamOptions, Camera, ScryptedInterface } from "@scrypted/sdk";
import { Stream } from "stream";
import { AmcrestCameraClient, AmcrestEvent } from "./amcrest-api";
const { log, deviceManager, mediaManager } = sdk;
class AmcrestCamera extends ScryptedDeviceBase implements VideoCamera, Camera, Settings {
eventStream: Stream;
constructor(nativeId: string) {
super(nativeId);
this.createMotionStream();
}
async createMotionStream() {
while (true) {
try {
this.motionDetected = false;
const api = new AmcrestCameraClient(this.storage.getItem('ip'), this.storage.getItem('username'), this.storage.getItem('password'));
for await (const event of api.listenForMotionEvents()) {
if (event === AmcrestEvent.MotionStart)
this.motionDetected = true;
else if (event === AmcrestEvent.MotionStop) {
this.motionDetected = false;
}
}
}
catch (e) {
console.error('event listener failure', e);
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
}
createClient() {
return new AmcrestCameraClient(this.storage.getItem('ip'), this.storage.getItem('username'), this.storage.getItem('password'));
}
async takePicture(): Promise<MediaObject> {
const api = new AmcrestCameraClient(this.storage.getItem('ip'), this.storage.getItem('username'), this.storage.getItem('password'));
return mediaManager.createMediaObject(api.jpegSnapshot(), 'image/jpeg');
}
async getVideoStreamOptions(): Promise<void | VideoStreamOptions[]> {
}
async getVideoStream(): Promise<MediaObject> {
const ip = this.storage.getItem('ip');
if (!ip) {
return null;
}
const username = this.storage.getItem("username")
const password = this.storage.getItem("password");
const url = `rtsp://${username}:${password}@${ip}/cam/realmonitor?channel=1&subtype=0`;
return mediaManager.createFFmpegMediaObject({
inputArguments: [
"-i",
url,
'-analyzeduration', '15000000',
'-probesize', '100000000',
"-reorder_queue_size",
"1024",
"-max_delay",
"20000000",
]
});
}
getSetting(key: string): string {
return this.storage.getItem(key);
}
async getSettings(): Promise<Setting[]> {
return [
{
key: 'ip',
title: 'Amcrest Camera IP',
placeholder: '192.168.1.100',
value: this.getSetting('ip'),
},
{
key: 'username',
title: 'Username',
value: this.getSetting('username'),
},
{
key: 'password',
title: 'Password',
value: this.getSetting('password'),
type: 'Password',
}
];
}
async putSetting(key: string, value: string | number) {
this.storage.setItem(key, value.toString());
}
}
class AmcrestProvider extends ScryptedDeviceBase implements DeviceProvider, Settings {
async getSettings(): Promise<Setting[]> {
return [
{
key: 'new-camera',
title: 'Add Camera',
placeholder: 'Camera name, e.g.: Back Yard Camera, Baby Camera, etc',
}
]
}
async putSetting(key: string, value: string | number) {
// generate a random id
var nativeId = Math.random().toString();
var name = value.toString();
deviceManager.onDeviceDiscovered({
nativeId,
name: name,
interfaces: [
ScryptedInterface.VideoCamera,
ScryptedInterface.Camera,
ScryptedInterface.MotionSensor,
ScryptedInterface.Settings,
],
type: ScryptedDeviceType.Camera,
});
}
async discoverDevices(duration: number) {
}
getDevice(nativeId: string): object {
return new AmcrestCamera(nativeId);
}
}
export default new AmcrestProvider();