mirror of
https://github.com/koush/scrypted.git
synced 2026-06-26 19:30:25 +01:00
amcrest/hikvision: add support for smart detections. publish.
This commit is contained in:
39
plugins/amcrest/package-lock.json
generated
39
plugins/amcrest/package-lock.json
generated
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.135",
|
||||
"version": "0.0.136",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.135",
|
||||
"version": "0.0.136",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"content-type": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.8"
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -23,23 +25,22 @@
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.8",
|
||||
"@types/node": "^20.11.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.29",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -77,15 +78,29 @@
|
||||
"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.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
|
||||
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
|
||||
"version": "20.11.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
|
||||
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
|
||||
"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.135",
|
||||
"version": "0.0.136",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -36,9 +36,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"content-type": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.8"
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,74 @@
|
||||
import { AuthFetchCredentialState, HttpFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
|
||||
import { Readable } from 'stream';
|
||||
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 { getDeviceInfo } from './probe';
|
||||
import { Point } from '@scrypted/sdk';
|
||||
|
||||
// {
|
||||
// "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
|
||||
// }
|
||||
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",
|
||||
@@ -18,6 +86,10 @@ export enum AmcrestEvent {
|
||||
DahuaTalkHangup = "Code=PassiveHungup;action=Start",
|
||||
DahuaCallDeny = "Code=HungupPhone;action=Pulse",
|
||||
DahuaTalkPulse = "Code=_CallNoAnswer_;action=Pulse",
|
||||
SmartMotionHuman = "Code=SmartMotionHuman;action=Start",
|
||||
SmartMotionVehicle = "Code=Vehicle;action=Start",
|
||||
CrossLineDetection = "Code=CrossLineDetection;action=Start",
|
||||
CrossRegionDetection = "Code=CrossRegionDetection;action=Start",
|
||||
}
|
||||
|
||||
export class AmcrestCameraClient {
|
||||
@@ -78,7 +150,8 @@ export class AmcrestCameraClient {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async listenEvents() {
|
||||
async listenEvents(): Promise<Destroyable> {
|
||||
const events = new EventEmitter();
|
||||
const url = `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[All]`;
|
||||
console.log('preparing event listener', url);
|
||||
|
||||
@@ -86,32 +159,102 @@ export class AmcrestCameraClient {
|
||||
url,
|
||||
responseType: 'readable',
|
||||
});
|
||||
const stream = response.body;
|
||||
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);
|
||||
});
|
||||
stream.socket.setKeepAlive(true);
|
||||
|
||||
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();
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
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', 'vehicle', 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
})()
|
||||
.catch(() => stream.destroy());
|
||||
return events as any as Destroyable;
|
||||
}
|
||||
|
||||
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, Reboot, RequestPictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, Lock, MediaObject, MediaStreamOptions, ObjectsDetected, 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 } from "./amcrest-api";
|
||||
import { AmcrestCameraClient, AmcrestEvent, AmcrestEventData } from "./amcrest-api";
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
@@ -28,6 +28,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
client: AmcrestCameraClient;
|
||||
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
|
||||
onvifIntercom = new OnvifIntercom(this);
|
||||
hasSmartDetection: boolean;
|
||||
|
||||
constructor(nativeId: string, provider: RtspProvider) {
|
||||
super(nativeId, provider);
|
||||
@@ -36,6 +37,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
this.storage.removeItem('amcrestDoorbell');
|
||||
}
|
||||
|
||||
this.hasSmartDetection = this.storage.getItem('hasSmartDetection') === 'true';
|
||||
this.updateDevice();
|
||||
this.updateDeviceInfo();
|
||||
}
|
||||
@@ -184,7 +186,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
if (idx.toString() !== channelNumber)
|
||||
return;
|
||||
}
|
||||
if (event === AmcrestEvent.MotionStart) {
|
||||
if (event === AmcrestEvent.MotionStart
|
||||
|| event === AmcrestEvent.SmartMotionHuman
|
||||
|| event === AmcrestEvent.SmartMotionVehicle
|
||||
|| event === AmcrestEvent.CrossLineDetection
|
||||
|| event === AmcrestEvent.CrossRegionDetection) {
|
||||
this.motionDetected = true;
|
||||
resetMotionTimeout();
|
||||
}
|
||||
@@ -231,6 +237,26 @@ 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;
|
||||
}
|
||||
|
||||
@@ -472,13 +498,19 @@ 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);
|
||||
}
|
||||
|
||||
@@ -521,7 +553,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';
|
||||
@@ -548,11 +580,11 @@ 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';
|
||||
}
|
||||
|
||||
|
||||
139
plugins/hikvision/package-lock.json
generated
139
plugins/hikvision/package-lock.json
generated
@@ -1,24 +1,23 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.139",
|
||||
"version": "0.0.140",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.139",
|
||||
"version": "0.0.140",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"content-disposition": "^0.5.4",
|
||||
"lodash": "^4.17.21",
|
||||
"xml2js": "^0.6.0"
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"content-type": "^1.0.5",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/content-disposition": "^0.5.8",
|
||||
"@types/node": "^18.15.11"
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -84,69 +83,50 @@
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/content-disposition": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz",
|
||||
"integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==",
|
||||
"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": "18.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/xml2js": {
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.11.tgz",
|
||||
"integrity": "sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==",
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"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/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.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.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
|
||||
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
@@ -200,52 +180,47 @@
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"@types/content-disposition": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz",
|
||||
"integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==",
|
||||
"@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": "18.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"@types/xml2js": {
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.11.tgz",
|
||||
"integrity": "sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==",
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.2.1"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"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=="
|
||||
"content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
|
||||
},
|
||||
"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.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
|
||||
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.139",
|
||||
"version": "0.0.140",
|
||||
"description": "Hikvision Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -37,13 +37,12 @@
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"content-disposition": "^0.5.4",
|
||||
"lodash": "^4.17.21",
|
||||
"xml2js": "^0.6.0"
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"content-type": "^1.0.5",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/content-disposition": "^0.5.8",
|
||||
"@types/node": "^18.15.11"
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/node": "^20.11.30"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 contentDisposition from 'content-disposition';
|
||||
import contentType from 'content-type';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { EventEmitter, Readable } from 'stream';
|
||||
import { Destroyable } from '../../rtsp/src/rtsp';
|
||||
@@ -173,7 +173,7 @@ export class HikvisionCameraAPI {
|
||||
|
||||
const ct = stream.headers['content-type'];
|
||||
// make content type parsable as content disposition filename
|
||||
const cd = contentDisposition.parse(ct.replace('/', ''));
|
||||
const cd = contentType.parse(ct);
|
||||
let { boundary } = cd.parameters;
|
||||
boundary = `--${boundary}`;
|
||||
const boundaryEnd = `${boundary}--`;
|
||||
@@ -200,7 +200,6 @@ export class HikvisionCameraAPI {
|
||||
|
||||
try {
|
||||
if (!headers['content-type'].includes('application/xml') && lastSmartDetection) {
|
||||
const cd = contentDisposition.parse(headers['content-disposition'] || 'empty');
|
||||
if (!headers['content-type']?.startsWith('image/jpeg')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user