Files
scrypted/plugins/pam-diff/src/main.ts

141 lines
4.8 KiB
TypeScript

import sdk, { MediaObject, ObjectDetection, ObjectDetectionGeneratorResult, ObjectDetectionGeneratorSession, ObjectDetectionModel, ObjectDetectionResult, ObjectDetectionSession, ObjectsDetected, ScryptedDeviceBase, VideoFrame } from '@scrypted/sdk';
import PD from 'pam-diff';
import P2P from 'pipe2pam';
import { PassThrough, Writable } from 'stream';
const defaultDifference = 9;
const defaultPercentage = 2;
class PamDiff extends ScryptedDeviceBase implements ObjectDetection {
async * generateObjectDetectionsInternal(videoFrames: AsyncGenerator<VideoFrame, any, unknown>, session: ObjectDetectionGeneratorSession): AsyncGenerator<ObjectDetectionGeneratorResult, any, unknown> {
videoFrames = await sdk.connectRPCObject(videoFrames);
const width = 640;
const height = 360;
const p2p: Writable = new P2P();
const pt = new PassThrough();
const pamDiff = new PD({
difference: parseInt(session.settings?.difference) || defaultDifference,
percent: parseInt(session.settings?.percent) || defaultPercentage,
response: session?.settings?.motionAsObjects ? 'blobs' : 'percent',
});
pt.pipe(p2p).pipe(pamDiff);
const queued: ObjectsDetected[] = [];
pamDiff.on('diff', async (data: any) => {
const trigger = data.trigger[0];
// console.log(trigger.blobs.length);
const { blobs } = trigger;
const detections: ObjectDetectionResult[] = [];
if (blobs?.length) {
for (const blob of blobs) {
detections.push(
{
className: 'motion',
score: 1,
boundingBox: [blob.minX, blob.minY, blob.maxX - blob.minX, blob.maxY - blob.minY],
}
)
}
}
else {
detections.push(
{
className: 'motion',
score: trigger.percent / 100,
}
)
}
const event: ObjectsDetected = {
timestamp: Date.now(),
inputDimensions: [width, height],
detections,
}
queued.push(event);
});
for await (const videoFrame of videoFrames) {
const header = `P7
WIDTH ${width}
HEIGHT ${height}
DEPTH 3
MAXVAL 255
TUPLTYPE RGB
ENDHDR
`;
const { image } = videoFrame;
const buffer = await image.toBuffer({
resize: (image.width !== width || image.height !== height) ? {
width,
height,
} : undefined,
format: 'rgb',
});
pt.write(Buffer.from(header));
pt.write(buffer);
if (!queued.length) {
yield {
__json_copy_serialize_children: true,
videoFrame,
detected: {
timestamp: Date.now(),
detections: [],
}
}
}
while (queued.length) {
yield {
__json_copy_serialize_children: true,
detected: queued.pop(),
videoFrame,
};
}
}
}
async generateObjectDetections(videoFrames: AsyncGenerator<VideoFrame, any, unknown>, session: ObjectDetectionGeneratorSession): Promise<AsyncGenerator<ObjectDetectionGeneratorResult, any, unknown>> {
return this.generateObjectDetectionsInternal(videoFrames, session);
}
async detectObjects(mediaObject: MediaObject, session?: ObjectDetectionSession): Promise<ObjectsDetected> {
throw new Error('can not run motion detection on image')
}
async getDetectionModel(): Promise<ObjectDetectionModel> {
return {
name: '@scrypted/pam-diff',
classes: ['motion'],
inputFormat: 'rgb',
inputSize: [640, 360],
settings: [
{
title: 'Motion Difference',
description: 'The color difference required to trigger motion on a pixel.',
key: 'difference',
value: this.storage.getItem('difference') || defaultDifference,
type: 'number',
},
{
title: 'Motion Percent',
description: 'The percentage of pixels required to trigger motion',
key: 'percent',
value: this.storage.getItem('percent]') || defaultPercentage,
type: 'number',
}
]
}
}
}
export default PamDiff;