mirror of
https://github.com/koush/scrypted.git
synced 2026-06-20 16:40:30 +01:00
rtsp/ffmpeg/snapshot: strip out the snapshot urls from ffmpeg and rtsp and direct the user to the snapshot plugin
This commit is contained in:
@@ -1,15 +1,3 @@
|
||||
# RTSP Cameras and Streams Plugin
|
||||
# FFmpeg Camera Plugin for Scrypted
|
||||
|
||||
## 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.
|
||||
Add FFmpeg Camera video streams to Scrypted. Cameras often have still image snapshot URLs. To use these snapshots you will also need the [Snapshot Plugin](#/component/plugin/install/@scrypted/snapshot).
|
||||
|
||||
4
plugins/ffmpeg-camera/package-lock.json
generated
4
plugins/ffmpeg-camera/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/ffmpeg-camera",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.9",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/ffmpeg-camera",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.9",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/ffmpeg-camera",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.9",
|
||||
"description": "FFmpeg Camera Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -4,7 +4,7 @@ import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import https from 'https';
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
const { log, deviceManager, mediaManager } = sdk;
|
||||
const { deviceManager, mediaManager } = sdk;
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
@@ -22,8 +22,21 @@ export abstract class CameraBase<T extends MediaStreamOptions> extends ScryptedD
|
||||
super(nativeId);
|
||||
}
|
||||
|
||||
getSnapshotUrl() {
|
||||
return this.storage.getItem('snapshotUrl');
|
||||
protected async takePictureUrl(snapshotUrl: string) {
|
||||
if (!this.snapshotAuth) {
|
||||
this.snapshotAuth = new AxiosDigestAuth({
|
||||
username: this.getUsername(),
|
||||
password: this.getPassword(),
|
||||
});
|
||||
}
|
||||
const response = await this.snapshotAuth.request({
|
||||
httpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
url: snapshotUrl,
|
||||
});
|
||||
|
||||
return mediaManager.createMediaObject(Buffer.from(response.data), response.headers['Content-Type'] || 'image/jpeg');
|
||||
}
|
||||
|
||||
async takePicture(option?: PictureOptions): Promise<MediaObject> {
|
||||
@@ -35,28 +48,7 @@ export abstract class CameraBase<T extends MediaStreamOptions> extends ScryptedD
|
||||
return this.pendingPicture;
|
||||
}
|
||||
|
||||
async takePictureThrottled(option?: PictureOptions): Promise<MediaObject> {
|
||||
const snapshotUrl = this.getSnapshotUrl();
|
||||
if (!snapshotUrl) {
|
||||
throw new Error('Camera has no snapshot URL');
|
||||
}
|
||||
|
||||
if (!this.snapshotAuth) {
|
||||
this.snapshotAuth = new AxiosDigestAuth({
|
||||
username: this.getUsername(),
|
||||
password: this.getPassword(),
|
||||
});
|
||||
}
|
||||
|
||||
const response = await this.snapshotAuth.request({
|
||||
httpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
url: snapshotUrl,
|
||||
});
|
||||
|
||||
return mediaManager.createMediaObject(Buffer.from(response.data), response.headers['Content-Type'] || 'image/jpeg');
|
||||
}
|
||||
abstract takePictureThrottled(option?: PictureOptions): Promise<MediaObject>;
|
||||
|
||||
async getPictureOptions(): Promise<PictureOptions[]> {
|
||||
return;
|
||||
@@ -92,21 +84,8 @@ export abstract class CameraBase<T extends MediaStreamOptions> extends ScryptedD
|
||||
|
||||
abstract createVideoStream(options?: T): Promise<MediaObject>;
|
||||
|
||||
async getSnapshotUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'snapshotUrl',
|
||||
title: 'Snapshot URL',
|
||||
placeholder: 'http://192.168.1.100[:80]/snapshot.jpg',
|
||||
value: this.getSnapshotUrl(),
|
||||
description: 'Optional: The snapshot URL that will returns the current JPEG image.'
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async getUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
...await this.getSnapshotUrlSettings(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -130,7 +109,7 @@ export abstract class CameraBase<T extends MediaStreamOptions> extends ScryptedD
|
||||
defaultStreamIndex = defaultStreamIndex || 0;
|
||||
return vsos?.[defaultStreamIndex];
|
||||
}
|
||||
|
||||
|
||||
async getStreamSettings(): Promise<Setting[]> {
|
||||
try {
|
||||
const vsos = await this.getVideoStreamOptions();
|
||||
@@ -208,16 +187,6 @@ export abstract class CameraBase<T extends MediaStreamOptions> extends ScryptedD
|
||||
|
||||
async putSetting(key: string, value: SettingValue) {
|
||||
this.putSettingBase(key, value);
|
||||
|
||||
if (key === 'snapshotUrl') {
|
||||
let interfaces = this.providedInterfaces;
|
||||
if (!value)
|
||||
interfaces = interfaces.filter(iface => iface !== ScryptedInterface.Camera)
|
||||
else
|
||||
interfaces.push(ScryptedInterface.Camera);
|
||||
|
||||
this.provider.updateDevice(this.nativeId, this.providedName, interfaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +240,7 @@ export abstract class CameraProviderBase<T extends MediaStreamOptions> extends S
|
||||
});
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: string | number) {
|
||||
async putSetting(value: string | number) {
|
||||
// generate a random id
|
||||
const nativeId = randomBytes(4).toString('hex');
|
||||
const name = value.toString();
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import sdk, { FFMpegInput, Intercom, MediaObject, ScryptedInterface, ScryptedMimeTypes, Setting, SettingValue } from "@scrypted/sdk";
|
||||
import sdk, { FFMpegInput, Intercom, MediaObject, PictureOptions, ScryptedInterface, ScryptedMimeTypes, Setting, SettingValue } from "@scrypted/sdk";
|
||||
import { CameraProviderBase, CameraBase, UrlMediaStreamOptions } from "./common";
|
||||
import { StorageSettings } from "../../../common/src/settings";
|
||||
import { ffmpegLogInitialOutput, safePrintFFmpegArguments } from "../../../common/src/media-helpers";
|
||||
import child_process, { ChildProcess } from "child_process";
|
||||
import { recommendDumbPlugins } from "./recommend";
|
||||
|
||||
const { log, deviceManager, mediaManager } = sdk;
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
class FFmpegCamera extends CameraBase<UrlMediaStreamOptions> implements Intercom {
|
||||
takePictureThrottled(option?: PictureOptions): Promise<MediaObject> {
|
||||
throw new Error("The RTSP Camera does not provide snapshots. Install the Snapshot Plugin if snapshots are available via an URL.");
|
||||
}
|
||||
|
||||
storageSettings = new StorageSettings(this, {
|
||||
ffmpegInputs: {
|
||||
title: 'FFmpeg Input Stream Arguments',
|
||||
@@ -52,7 +57,6 @@ class FFmpegCamera extends CameraBase<UrlMediaStreamOptions> implements Intercom
|
||||
// this might be usable as a url so check that.
|
||||
let url: string;
|
||||
try {
|
||||
const parsedUrl = new URL(ffmpegInput);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
@@ -96,7 +100,6 @@ class FFmpegCamera extends CameraBase<UrlMediaStreamOptions> implements Intercom
|
||||
|
||||
async getUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
...await this.getSnapshotUrlSettings(),
|
||||
...await this.getFFmpegInputSettings(),
|
||||
];
|
||||
}
|
||||
@@ -121,6 +124,11 @@ class FFmpegCamera extends CameraBase<UrlMediaStreamOptions> implements Intercom
|
||||
}
|
||||
|
||||
class FFmpegProvider extends CameraProviderBase<UrlMediaStreamOptions> {
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
recommendDumbPlugins();
|
||||
}
|
||||
|
||||
createCamera(nativeId: string): FFmpegCamera {
|
||||
return new FFmpegCamera(nativeId, this);
|
||||
}
|
||||
|
||||
@@ -5,3 +5,10 @@ export async function recommendRebroadcast() {
|
||||
'@scrypted/prebuffer-mixin': 'Rebroadcast',
|
||||
});
|
||||
}
|
||||
|
||||
export async function recommendDumbPlugins() {
|
||||
alertRecommendedPlugins({
|
||||
'@scrypted/snapshot': 'Snapshot Plugin',
|
||||
'@scrypted/opencv': 'OpenCV Motion Detection',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,34 +1,3 @@
|
||||
# RTSP Cameras and Streams Plugin
|
||||
# RTSP Camera Plugin for Scrypted
|
||||
|
||||
## 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.
|
||||
|
||||
# Setup and Configuration
|
||||
|
||||
1. Once the plugin is installed, click "Add a device" and give the RTSP Camera a name.
|
||||
|
||||
2. Then under "Settings" and then "General", type in the username and password for your RTSP Stream. Click the green arrow to save the changes.
|
||||
|
||||
3. Then add the RTSP Stream Link.
|
||||
|
||||
ie: rtsp://<ip-address>:<port>/<channel>/<mode>
|
||||
|
||||
*Please note that RTSP Streams differ between each camera make and model.*
|
||||
|
||||
If your camera has support for a substream, click the "Add" button to add another RTSP Stream URL.
|
||||
|
||||
4. Once you are done, Click "Save RTSP Stream URL"
|
||||
|
||||
Enable "No Audio" if the camera does not have audio or if you want to mute audio.
|
||||
|
||||
Add RTSP Camera video streams to Scrypted. RTSP Cameras often have still image snapshot URLs. To use these snapshots you will also need the [Snapshot Plugin](#/component/plugin/install/@scrypted/snapshot).
|
||||
|
||||
4
plugins/rtsp/package-lock.json
generated
4
plugins/rtsp/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/rtsp",
|
||||
"version": "0.0.46",
|
||||
"version": "0.0.47",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/rtsp",
|
||||
"version": "0.0.46",
|
||||
"version": "0.0.47",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/rtsp",
|
||||
"version": "0.0.46",
|
||||
"version": "0.0.47",
|
||||
"description": "RTSP Cameras and Streams Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { alertRecommendedPlugins } from "@scrypted/common/src/alert-recommended-plugins";
|
||||
|
||||
export async function recommendRebroadcast() {
|
||||
alertRecommendedPlugins({
|
||||
'@scrypted/prebuffer-mixin': 'Rebroadcast',
|
||||
});
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import sdk, { Setting, MediaObject, MediaStreamOptions, ScryptedInterface, FFMpegInput, PictureOptions, SettingValue } from "@scrypted/sdk";
|
||||
import sdk, { Setting, MediaObject, ScryptedInterface, FFMpegInput, PictureOptions, SettingValue } from "@scrypted/sdk";
|
||||
import { EventEmitter } from "stream";
|
||||
import https from 'https';
|
||||
import { CameraProviderBase, CameraBase, UrlMediaStreamOptions } from "../../ffmpeg-camera/src/common";
|
||||
import url from 'url';
|
||||
import { recommendDumbPlugins } from "./../../ffmpeg-camera/src/recommend";
|
||||
|
||||
export { UrlMediaStreamOptions } from "../../ffmpeg-camera/src/common";
|
||||
|
||||
const { log, deviceManager, mediaManager } = sdk;
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
export class RtspCamera extends CameraBase<UrlMediaStreamOptions> {
|
||||
takePictureThrottled(option?: PictureOptions): Promise<MediaObject> {
|
||||
throw new Error("The RTSP Camera does not provide snapshots. Install the Snapshot Plugin if snapshots are available via an URL.");
|
||||
}
|
||||
|
||||
createRtspMediaStreamOptions(url: string, index: number) {
|
||||
return {
|
||||
id: `channel${index}`,
|
||||
@@ -133,7 +133,6 @@ export class RtspCamera extends CameraBase<UrlMediaStreamOptions> {
|
||||
|
||||
async getUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
...await this.getSnapshotUrlSettings(),
|
||||
...await this.getRtspUrlSettings(),
|
||||
];
|
||||
}
|
||||
@@ -194,28 +193,11 @@ export abstract class RtspSmartCamera extends RtspCamera {
|
||||
}
|
||||
|
||||
async takePictureThrottled(option?: PictureOptions) {
|
||||
if (this.showSnapshotUrlOverride() && this.getSnapshotUrl()) {
|
||||
return super.takePictureThrottled(option);
|
||||
}
|
||||
|
||||
return this.takeSmartCameraPicture(option);;
|
||||
}
|
||||
|
||||
abstract takeSmartCameraPicture(options?: PictureOptions): Promise<MediaObject>;
|
||||
|
||||
async getSnapshotUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'snapshotUrl',
|
||||
group: 'Advanced',
|
||||
title: 'Snapshot URL Override',
|
||||
placeholder: 'http://192.168.1.100[:80]/snapshot.jpg',
|
||||
value: this.storage.getItem('snapshotUrl'),
|
||||
description: 'Override the snapshot URL that will returns the current JPEG image.'
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async getRtspUrlSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
@@ -253,12 +235,6 @@ export abstract class RtspSmartCamera extends RtspCamera {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.showSnapshotUrlOverride()) {
|
||||
ret.push(
|
||||
... await this.getSnapshotUrlSettings(),
|
||||
);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -304,10 +280,6 @@ export abstract class RtspSmartCamera extends RtspCamera {
|
||||
return true;
|
||||
}
|
||||
|
||||
showSnapshotUrlOverride() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getHttpAddress() {
|
||||
return `${this.getIPAddress()}:${this.storage.getItem('httpPort') || 80}`;
|
||||
}
|
||||
@@ -316,7 +288,7 @@ export abstract class RtspSmartCamera extends RtspCamera {
|
||||
this.storage.setItem('httpPort', port);
|
||||
}
|
||||
|
||||
getRtspUrlOverride(options?: MediaStreamOptions) {
|
||||
getRtspUrlOverride() {
|
||||
if (!this.showRtspUrlOverride())
|
||||
return;
|
||||
return this.storage.getItem('rtspUrlOverride');
|
||||
@@ -350,6 +322,11 @@ export abstract class RtspSmartCamera extends RtspCamera {
|
||||
}
|
||||
|
||||
export class RtspProvider extends CameraProviderBase<UrlMediaStreamOptions> {
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
recommendDumbPlugins();
|
||||
}
|
||||
|
||||
createCamera(nativeId: string): RtspCamera {
|
||||
return new RtspCamera(nativeId, this);
|
||||
}
|
||||
|
||||
5
plugins/snapshot/.gitignore
vendored
Normal file
5
plugins/snapshot/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/
|
||||
.venv
|
||||
9
plugins/snapshot/.npmignore
Normal file
9
plugins/snapshot/.npmignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
*.map
|
||||
fs
|
||||
src
|
||||
.vscode
|
||||
dist/*.js
|
||||
.venv
|
||||
22
plugins/snapshot/.vscode/launch.json
vendored
Normal file
22
plugins/snapshot/.vscode/launch.json
vendored
Normal 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": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"sourceMaps": true,
|
||||
"localRoot": "${workspaceFolder}/out",
|
||||
"remoteRoot": "/plugin/",
|
||||
"type": "pwa-node"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugins/snapshot/.vscode/settings.json
vendored
Normal file
4
plugins/snapshot/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
}
|
||||
20
plugins/snapshot/.vscode/tasks.json
vendored
Normal file
20
plugins/snapshot/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "scrypted: deploy+debug",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "silent",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
|
||||
},
|
||||
]
|
||||
}
|
||||
3
plugins/snapshot/README.md
Normal file
3
plugins/snapshot/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Snapshot Plugin for Scrypted
|
||||
|
||||
Add custom Snapshot URLs to any camera.
|
||||
211
plugins/snapshot/package-lock.json
generated
Normal file
211
plugins/snapshot/package-lock.json
generated
Normal file
@@ -0,0 +1,211 @@
|
||||
{
|
||||
"name": "@scrypted/snapshot",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/snapshot",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@types/node": "^16.6.1",
|
||||
"axios": "^0.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@koush/werift": "file:../external/werift/packages/webrtc",
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.0.173",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"webpack": "^5.59.0"
|
||||
},
|
||||
"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": {
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
|
||||
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
|
||||
"dependencies": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth/node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz",
|
||||
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA=="
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
|
||||
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
|
||||
"requires": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
"@koush/werift": "file:../external/werift/packages/webrtc",
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@types/node": "^16.9.0",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack": "^5.59.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz",
|
||||
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA=="
|
||||
},
|
||||
"auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
|
||||
}
|
||||
},
|
||||
"version": "0.0.2"
|
||||
}
|
||||
36
plugins/snapshot/package.json
Normal file
36
plugins/snapshot/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@scrypted/snapshot",
|
||||
"version": "0.0.2",
|
||||
"description": "Snapshot Plugin for Scrypted",
|
||||
"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-webpack": "scrypted-webpack"
|
||||
},
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"snapshot",
|
||||
"camera"
|
||||
],
|
||||
"scrypted": {
|
||||
"name": "Snapshot Plugin",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"MixinProvider"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@types/node": "^16.6.1",
|
||||
"axios": "^0.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
}
|
||||
90
plugins/snapshot/src/main.ts
Normal file
90
plugins/snapshot/src/main.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import sdk, { Camera, MediaObject, MixinProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue } from "@scrypted/sdk";
|
||||
import { SettingsMixinDeviceBase } from "@scrypted/common/src/settings-mixin"
|
||||
import { StorageSettings } from "@scrypted/common/src/settings"
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import https from 'https';
|
||||
import axios, { Axios } from "axios";
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
class SnapshotMixin extends SettingsMixinDeviceBase<Camera> implements Camera {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
snapshotUrl: {
|
||||
title: 'Snapshot URL',
|
||||
}
|
||||
});
|
||||
axiosClient: Axios | AxiosDigestAuth;
|
||||
|
||||
constructor(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }, providerNativeId: string) {
|
||||
super(mixinDevice, mixinDeviceState, {
|
||||
providerNativeId,
|
||||
mixinDeviceInterfaces,
|
||||
group: 'Snapshot',
|
||||
groupKey: 'snapshot',
|
||||
});
|
||||
}
|
||||
|
||||
async takePicture(): Promise<MediaObject> {
|
||||
if (!this.axiosClient) {
|
||||
let username: string;
|
||||
let password: string;
|
||||
|
||||
if (this.mixinDeviceInterfaces.includes(ScryptedInterface.Settings)) {
|
||||
const settings = await this.mixinDevice.getSettings();
|
||||
username = settings?.find(setting => setting.key === 'username')?.value?.toString();
|
||||
password = settings?.find(setting => setting.key === 'userpasswordname')?.value?.toString();
|
||||
}
|
||||
|
||||
if (username && password) {
|
||||
this.axiosClient = new AxiosDigestAuth({
|
||||
username,
|
||||
password,
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.axiosClient = axios;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await this.axiosClient.request({
|
||||
httpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'arraybuffer',
|
||||
url: this.storageSettings.values.snapshotUrl,
|
||||
});
|
||||
|
||||
return mediaManager.createMediaObject(Buffer.from(response.data), 'image/jpeg');
|
||||
}
|
||||
|
||||
async getPictureOptions() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getMixinSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
putMixinSetting(key: string, value: SettingValue) {
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
class SnapshotPlugin extends ScryptedDeviceBase implements MixinProvider {
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
if (type === ScryptedDeviceType.Camera && interfaces.includes(ScryptedInterface.VideoCamera))
|
||||
return [ScryptedInterface.Camera, ScryptedInterface.Settings];
|
||||
return undefined;
|
||||
|
||||
}
|
||||
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise<any> {
|
||||
return new SnapshotMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.nativeId);
|
||||
}
|
||||
|
||||
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export default new SnapshotPlugin();
|
||||
Reference in New Issue
Block a user