Skip to content

Commit

Permalink
v1.1.14
Browse files Browse the repository at this point in the history
  • Loading branch information
seydx committed Apr 23, 2022
1 parent 1968ab7 commit 948c9db
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 46 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
# Changelog
All notable changes to this project will be documented in this file.

# v1.1.12 - 2022-04-23
# v1.1.14 - 2022-04-23

## Other Changes
- **MQTT:** When motion is detected, two MQTT messages are now published on following topics:
1. **camera.ui/notifications**: Contains all notifications AFTER motion has been detected AND recorded.
2. **camera.ui/motion** _(can be changed in the interface):_ Contains motion event (before something is recorded).
- Deprecated FFmpeg arguments will be auto replaced now
- Minor improvements to probe stream

## Bugfixes
- Fixed an issue where changing camera settings via the interface did not work

# v1.1.12 / v1.1.13 - 2022-04-23

## Other Changes
- Improved probe stream
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "camera.ui",
"version": "1.1.13",
"version": "1.1.14",
"description": "NVR like user interface for RTSP capable cameras.",
"author": "SeydX (https://github.com/SeydX/camera.ui)",
"scripts": {
Expand Down
26 changes: 13 additions & 13 deletions src/common/ffmpeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,18 @@ export const getAndStoreSnapshot = (
}

const videoProcessor = ConfigService.ui.options.videoProcessor;

const videoConfig = cameraUtils.generateVideoConfig(camera.videoConfig);
const videoWidth = videoConfig.maxWidth;
const videoHeight = videoConfig.maxHeight;
const destination = storeSnapshot ? `${recordingPath}/${fileName}${isPlaceholder ? '@2' : ''}.jpeg` : '-';
const controller = CameraController.cameras.get(camera.name);

let ffmpegInput = [
...cameraUtils.generateInputSource(videoConfig, fromSubSource ? videoConfig.subSource : false).split(/\s+/),
];

const videoWidth = videoConfig.maxWidth;
const videoHeight = videoConfig.maxHeight;

const destination = storeSnapshot ? `${recordingPath}/${fileName}${isPlaceholder ? '@2' : ''}.jpeg` : '-';
ffmpegInput = cameraUtils.checkDeprecatedFFmpegArguments(controller?.media?.codecs?.ffmpegVersion, ffmpegInput);

const controller = CameraController.cameras.get(camera.name);
if (!fromSubSource && camera.prebuffering && controller?.prebuffer) {
try {
ffmpegInput = await controller.prebuffer.getVideo();
Expand Down Expand Up @@ -296,16 +296,16 @@ export const storeVideo = (camera, recordingPath, fileName, recordingTimer) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const videoProcessor = ConfigService.ui.options.videoProcessor;

const videoConfig = cameraUtils.generateVideoConfig(camera.videoConfig);
let ffmpegInput = [...cameraUtils.generateInputSource(videoConfig).split(/\s+/)];

const videoName = `${recordingPath}/${fileName}.mp4`;
const videoWidth = videoConfig.maxWidth;
const videoHeight = videoConfig.maxHeight;
const vcodec = videoConfig.vcodec;

const controller = CameraController.cameras.get(camera.name);

let ffmpegInput = [...cameraUtils.generateInputSource(videoConfig).split(/\s+/)];
ffmpegInput = cameraUtils.checkDeprecatedFFmpegArguments(controller?.media?.codecs?.ffmpegVersion, ffmpegInput);

if (camera.prebuffering && controller?.prebuffer) {
try {
ffmpegInput = await controller.prebuffer.getVideo();
Expand Down Expand Up @@ -400,13 +400,13 @@ export const handleFragmentsRequests = async function* (camera) {
log.debug('Video fragments requested from interface', camera.name);

const videoConfig = cameraUtils.generateVideoConfig(camera.videoConfig);
let ffmpegInput = [...cameraUtils.generateInputSource(videoConfig).split(/\s+/)];

const audioArguments = ['-acodec', 'copy'];
const videoArguments = ['-vcodec', 'copy'];

const controller = CameraController.cameras.get(camera.name);

let ffmpegInput = [...cameraUtils.generateInputSource(videoConfig).split(/\s+/)];
ffmpegInput = cameraUtils.checkDeprecatedFFmpegArguments(controller?.media?.codecs?.ffmpegVersion, ffmpegInput);

if (camera.prebuffering && controller?.prebuffer) {
try {
log.debug('Setting prebuffer stream as input', camera.name);
Expand Down
12 changes: 6 additions & 6 deletions src/controller/camera/camera.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default class CameraController {
const videoanalysisService = new VideoAnalysisService(
camera,
prebufferService,
mediaService,
CameraController.#controller,
CameraController.#socket
);
Expand Down Expand Up @@ -96,16 +97,15 @@ export default class CameraController {

static async reconfigureController(cameraName) {
const controller = CameraController.cameras.get(cameraName);
const camera = ConfigService.ui.cameras.find((camera) => camera.name === cameraName);

if (!controller) {
throw new Error(`Can not reconfigure controller, controller for ${cameraName} not found!`);
}

if (camera.disable) {
if (camera?.disable) {
return;
}

const camera = ConfigService.ui.cameras.find((camera) => camera.name === cameraName);
if (!controller) {
throw new Error(`Can not reconfigure controller, controller for ${cameraName} not found!`);
}

if (!camera) {
throw new Error(`Unexpected error occured during reconfiguring controller for ${cameraName}`);
Expand Down
5 changes: 3 additions & 2 deletions src/controller/camera/services/media.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default class MediaService {
this.cameraName = camera.name;

this.codecs = {
ffmpegVersion: null,
probe: false,
timedout: false,
audio: [],
Expand Down Expand Up @@ -67,7 +68,7 @@ export default class MediaService {

stderr.on('line', (line) => {
if (lines === 0) {
ConfigService.ffmpegVersion = line.split(' ')[2];
this.codecs.ffmpegVersion = ConfigService.ffmpegVersion = line.split(' ')[2];
}

const bitrateLine = line.includes('start: ') && line.includes('bitrate: ') ? line.split('bitrate: ')[1] : false;
Expand Down Expand Up @@ -109,7 +110,7 @@ export default class MediaService {
this.codecs.timedout = true;
cp.kill('SIGKILL');
}
}, 5000);
}, 10000);
});
}
}
4 changes: 3 additions & 1 deletion src/controller/camera/services/prebuffer.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default class PrebufferService {
let incompatibleAudio = audioSourceFound && !probeAudio.some((codec) => compatibleAudio.test(codec));
let probeTimedOut = this.#mediaService.codecs.timedout;

const ffmpegInput = [
let ffmpegInput = [
'-hide_banner',
'-loglevel',
'error',
Expand All @@ -177,6 +177,8 @@ export default class PrebufferService {
...cameraUtils.generateInputSource(videoConfig).split(/\s+/),
];

ffmpegInput = cameraUtils.checkDeprecatedFFmpegArguments(this.#mediaService.codecs.ffmpegVersion, ffmpegInput);

const audioArguments = [];

/*if (audioEnabled && incompatibleAudio) {
Expand Down
3 changes: 3 additions & 0 deletions src/controller/camera/services/stream.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ export default class StreamService {
const cameraSetting = Settings.find((camera) => camera && camera.name === this.cameraName);

const videoConfig = cameraUtils.generateVideoConfig(this.#camera.videoConfig);

let ffmpegInput = [...cameraUtils.generateInputSource(videoConfig).split(/\s+/)];
ffmpegInput = cameraUtils.checkDeprecatedFFmpegArguments(this.#mediaService.codecs.ffmpegVersion, ffmpegInput);

let prebuffer = null;

if (this.#camera.prebuffering && this.#prebufferService) {
Expand Down
13 changes: 9 additions & 4 deletions src/controller/camera/services/videoanalysis.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default class VideoAnalysisService {
#controller;
#socket;
#prebufferService;
#mediaService;

videoanalysisSession = null;
killed = false;
Expand All @@ -58,13 +59,14 @@ export default class VideoAnalysisService {

finishLaunching = false;

constructor(camera, prebufferService, controller, socket) {
constructor(camera, prebufferService, mediaService, controller, socket) {
//log.debug('Initializing video analysis', camera.name);

this.#camera = camera;
this.#controller = controller;
this.#socket = socket;
this.#prebufferService = prebufferService;
this.#mediaService = mediaService;

this.cameraName = camera.name;

Expand Down Expand Up @@ -112,12 +114,15 @@ export default class VideoAnalysisService {
this.resetVideoAnalysis();

const videoConfig = cameraUtils.generateVideoConfig(this.#camera.videoConfig);
let input = cameraUtils.generateInputSource(videoConfig, videoConfig.subSource).split(/\s+/);

let ffmpegInput = cameraUtils.generateInputSource(videoConfig, videoConfig.subSource).split(/\s+/);
ffmpegInput = cameraUtils.checkDeprecatedFFmpegArguments(this.#mediaService.codecs.ffmpegVersion, ffmpegInput);

let withPrebuffer = false;

if (this.#camera.prebuffering && videoConfig.subSource === videoConfig.source) {
try {
input = withPrebuffer = await this.#prebufferService.getVideo();
ffmpegInput = withPrebuffer = await this.#prebufferService.getVideo();
} catch {
// retry
log.debug(
Expand Down Expand Up @@ -153,7 +158,7 @@ export default class VideoAnalysisService {
}
}

this.videoanalysisSession = await this.#startVideoAnalysis(input, videoConfig);
this.videoanalysisSession = await this.#startVideoAnalysis(ffmpegInput, videoConfig);

if (!withPrebuffer) {
const timer = this.#millisUntilTime('04:00');
Expand Down
21 changes: 21 additions & 0 deletions src/controller/camera/utils/camera.utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable unicorn/number-literal-case */
'use-strict';

import compareVersions from 'compare-versions';
import { createServer } from 'net';
import { once } from 'events';
import readline from 'readline';
Expand Down Expand Up @@ -354,3 +355,23 @@ export const generateVideoConfig = (videoConfig) => {

return config;
};

export const checkDeprecatedFFmpegArguments = (ffmpegVersion, ffmpegArguments) => {
if (!ffmpegVersion) {
return ffmpegArguments;
}

let ffmpegArgumentsArray = !Array.isArray(ffmpegArguments) ? ffmpegArguments.split(' ') : [...ffmpegArguments];

if (compareVersions.compare(ffmpegVersion, '5.0.0', '>=')) {
ffmpegArgumentsArray = ffmpegArgumentsArray.map((argument) => {
if (argument === '-stimeout') {
argument = '-timeout';
}

return argument;
});
}

return ffmpegArgumentsArray;
};
22 changes: 9 additions & 13 deletions src/controller/event/event.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ export default class EventController {
endpoint: CameraSettings.webhookUrl,
};

const mqttPublishSettings = {
topic: CameraSettings.mqttTopic,
};

const webpushSettings = {
publicKey: SettingsDB.webpush.publicKey,
privateKey: SettingsDB.webpush.privateKey,
Expand Down Expand Up @@ -165,7 +161,7 @@ export default class EventController {
const { notification, notify } = await EventController.#handleNotification(motionInfo);

// 1)
await EventController.#publishMqtt(cameraName, notification, mqttPublishSettings);
await EventController.#publishMqtt(cameraName, notification, notificationsSettings.active);

// 2)
await EventController.#sendWebhook(
Expand Down Expand Up @@ -598,18 +594,18 @@ export default class EventController {
}
}

static async #publishMqtt(cameraName, notification, mqttPublishSettings) {
static async #publishMqtt(cameraName, notification, notificationActive) {
if (!notificationActive) {
return log.debug('Notifications not enabled, skip MQTT (notification)..', cameraName);
}

try {
const mqttClient = EventController.#controller.motionController?.mqttClient;

if (mqttClient && mqttClient.connected) {
if (!mqttPublishSettings.topic) {
return log.debug('No MQTT Publish Topic defined, skip MQTT..');
}

mqttClient.publish(mqttPublishSettings.topic, JSON.stringify(notification));
if (mqttClient?.connected) {
mqttClient.publish('camera.ui/notifications', JSON.stringify(notification));
} else {
return log.debug('MQTT client not connected, skip MQTT..');
return log.debug('MQTT client not connected, skip MQTT (notification)..');
}
} catch (error) {
log.info('An error occured during publishing mqtt message', cameraName, 'events');
Expand Down
23 changes: 20 additions & 3 deletions src/controller/motion/motion.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -690,9 +690,10 @@ export default class MotionController {
}

if (camera) {
const generalSettings = await Database.interfaceDB.chain.get('settings').get('general').cloneDeep().value();
const atHome = generalSettings?.atHome || false;
const cameraExcluded = (generalSettings?.exclude || []).includes(camera.name);
const settingsDatabase = await Database.interfaceDB.chain.get('settings').cloneDeep().value();
const cameraSettings = settingsDatabase?.cameras.find((cam) => cam.name === cameraName);
const atHome = settingsDatabase?.general?.atHome || false;
const cameraExcluded = (settingsDatabase?.general?.exclude || []).includes(camera.name);

if (atHome && !cameraExcluded) {
const message = `Skip motion trigger. At Home is active and ${camera.name} is not excluded!`;
Expand All @@ -714,6 +715,22 @@ export default class MotionController {
MotionController.#controller.emit('motion', camera.name, triggerType, state, event); // used for extern controller, like Homebridge

if (camera.recordOnMovement) {
const mqttClient = MotionController.mqttClient;

if (mqttClient?.connected && cameraSettings?.mqttTopic) {
mqttClient.publish(
cameraSettings.mqttTopic,
JSON.stringify({
camera: camera.name,
state: state,
type: triggerType,
event: event,
})
);
} else {
log.debug('No MQTT Publish Topic defined, skip MQTT (motion)..');
}

const recordingSettings = await Database.interfaceDB.chain
.get('settings')
.get('recordings')
Expand Down

0 comments on commit 948c9db

Please sign in to comment.