Skip to content

Commit

Permalink
Camera: Replace ffmpeg-fluent by native commands (#2132)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Gilles authored Oct 14, 2024
1 parent 8d2fc30 commit a000b56
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 220 deletions.
3 changes: 1 addition & 2 deletions server/services/rtsp-camera/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ const RtspCameraHandler = require('./lib');
const RtspCameraController = require('./api/rtspCamera.controller');

module.exports = function RtspCameraService(gladys, serviceId) {
const ffmpeg = require('fluent-ffmpeg');
const device = new RtspCameraHandler(gladys, ffmpeg, childProcess, serviceId);
const device = new RtspCameraHandler(gladys, childProcess, serviceId);
/**
* @public
* @description This function starts service.
Expand Down
61 changes: 33 additions & 28 deletions server/services/rtsp-camera/lib/getImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,55 +38,60 @@ async function getImage(device) {
this.gladys.config.tempFolder,
`camera-${device.id}-${now.getMilliseconds()}-${now.getSeconds()}-${now.getMinutes()}-${now.getHours()}.jpg`,
);
// we create a writestream
const writeStream = fse.createWriteStream(filePath);
const outputOptions = [
'-vframes 1',
'-qscale:v 15', // Effective range for JPEG is 2-31 with 31 being the worst quality.
];

const args = ['-i', cameraUrlParam.value, '-f', 'image2', '-vframes', '1', '-qscale:v', '15'];

args.push('-vf');
switch (cameraRotationParam.value) {
case DEVICE_ROTATION.DEGREES_90:
outputOptions.push('-vf scale=640:-1,transpose=1'); // Rotate 90
args.push('scale=640:-1,transpose=1'); // Rotate 90
break;
case DEVICE_ROTATION.DEGREES_180:
outputOptions.push('-vf scale=640:-1,transpose=1,transpose=1'); // Rotate 180
args.push('scale=640:-1,transpose=1,transpose=1'); // Rotate 180
break;
case DEVICE_ROTATION.DEGREES_270:
outputOptions.push('-vf scale=640:-1,transpose=2'); // Rotate 270
args.push('scale=640:-1,transpose=2'); // Rotate 270
break;
default:
outputOptions.push('-vf scale=640:-1'); // Rotate 0
args.push('scale=640:-1'); // Rotate 0
break;
}

// Send a camera thumbnail to this stream
// Add a timeout to prevent ffmpeg from running forever
this.ffmpeg(cameraUrlParam.value, { timeout: 10 })
.format('image2')
.outputOptions(outputOptions)
.output(writeStream)
.on('end', async () => {
// add destination file path
args.push(filePath);

logger.debug(`Getting camera image on URL ${cameraUrlParam.value}`);
this.childProcess.execFile(
'ffmpeg',
args,
{
timeout: 10 * 1000, // 10 second max
},
async (error, stdout, stderr) => {
if (error) {
logger.warn(error);
await fse.remove(filePath);
return reject(error);
}
logger.debug('Camera image saved to disk. Reading disk.');
let image;
try {
image = await fse.readFile(filePath);
} catch (e) {
reject(e);
return;
await fse.remove(filePath);
return reject(e);
}
logger.debug('Camera image read from disk, converting to base64');

// convert binary data to base64 encoded string
const cameraImageBase = Buffer.from(image).toString('base64');
const cameraImage = `image/png;base64,${cameraImageBase}`;
const cameraImage = `image/jpg;base64,${cameraImageBase}`;
logger.debug('Camera converted to base64, resolving.');
resolve(cameraImage);
await fse.remove(filePath);
})
.on('error', async (err, stdout, stderr) => {
logger.debug(`Cannot process video: ${err.message}`);
logger.debug(stderr);
reject(err.message);
await fse.remove(filePath);
})
.run();
return null;
},
);
});
}

Expand Down
6 changes: 2 additions & 4 deletions server/services/rtsp-camera/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ const { stopStreaming } = require('./stopStreaming');
/**
* @description Add ability to connect to RTSP camera.
* @param {object} gladys - Gladys instance.
* @param {object} ffmpeg - Ffmpeg library.
* @param {object} childProcess - ChildProcess library.
* @param {string} serviceId - UUID of the service in DB.
* @example
* const rtspCameraHandler = new RtspCameraHandler(gladys, ffmpeg, serviceId);
* const rtspCameraHandler = new RtspCameraHandler(gladys, childProcess, serviceId);
*/
const RtspCameraHandler = function RtspCameraHandler(gladys, ffmpeg, childProcess, serviceId) {
const RtspCameraHandler = function RtspCameraHandler(gladys, childProcess, serviceId) {
this.gladys = gladys;
this.ffmpeg = ffmpeg;
this.childProcess = childProcess;
this.serviceId = serviceId;
this.checkIfLiveActiveFrequencyInSeconds = 10;
Expand Down
61 changes: 0 additions & 61 deletions server/services/rtsp-camera/package-lock.json

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

1 change: 0 additions & 1 deletion server/services/rtsp-camera/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"dependencies": {
"bluebird": "^3.7.2",
"bottleneck": "^2.19.5",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^8.0.1"
}
}
33 changes: 0 additions & 33 deletions server/test/services/rtsp-camera/FfmpegMock.test.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const { expect, assert } = require('chai');
const fse = require('fs-extra');
const path = require('path');
const { fake, assert: fakeAssert } = require('sinon');
const FfmpegMock = require('./FfmpegMock.test');
const RtspCameraManager = require('../../../services/rtsp-camera/lib');
const { NotFoundError } = require('../../../utils/coreErrors');

Expand Down Expand Up @@ -41,12 +40,7 @@ describe('Camera.convertLocalStreamToGateway', () => {
const indexFilePath = path.join(folderPath, 'index.m3u8');
const keyfilePath = path.join(folderPath, 'index.m3u8.key');
const videoFilePath = path.join(folderPath, 'index0.ts');
const rtspCameraManager = new RtspCameraManager(
gladys,
FfmpegMock,
childProcessMock,
'de051f90-f34a-4fd5-be2e-e502339ec9bc',
);
const rtspCameraManager = new RtspCameraManager(gladys, childProcessMock, 'de051f90-f34a-4fd5-be2e-e502339ec9bc');
before(async () => {
await fse.ensureDir(folderPath);
await fse.writeFile(indexFilePath, 'this is index');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const { expect } = require('chai');
const fse = require('fs-extra');
const path = require('path');
const { fake, assert: fakeAssert } = require('sinon');
const FfmpegMock = require('./FfmpegMock.test');
const RtspCameraManager = require('../../../services/rtsp-camera/lib');

const device = {
Expand Down Expand Up @@ -51,12 +50,7 @@ describe('Camera.onNewCameraFile', () => {
await fse.remove(folderPath);
});
beforeEach(() => {
rtspCameraManager = new RtspCameraManager(
gladys,
FfmpegMock,
childProcessMock,
'de051f90-f34a-4fd5-be2e-e502339ec9bc',
);
rtspCameraManager = new RtspCameraManager(gladys, childProcessMock, 'de051f90-f34a-4fd5-be2e-e502339ec9bc');
rtspCameraManager.sendCameraFileToGatewayLimited = fake.resolves(null);
});
it('should return directly, no live stream', async () => {
Expand Down Expand Up @@ -111,12 +105,7 @@ describe('Camera.onNewCameraFile', () => {
fakeAssert.calledWith(eventEmitter.emit, 'gateway-ready');
});
it('should upload a file that fail, and return null', async () => {
rtspCameraManager = new RtspCameraManager(
gladys,
FfmpegMock,
childProcessMock,
'de051f90-f34a-4fd5-be2e-e502339ec9bc',
);
rtspCameraManager = new RtspCameraManager(gladys, childProcessMock, 'de051f90-f34a-4fd5-be2e-e502339ec9bc');
rtspCameraManager.sendCameraFileToGatewayLimited = fake.rejects(null);
rtspCameraManager.liveStreams.set('my-camera', {
isGladysGateway: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const fse = require('fs-extra');
const path = require('path');
const { fake, assert: fakeAssert } = require('sinon');
const FfmpegMock = require('./FfmpegMock.test');
const RtspCameraManager = require('../../../services/rtsp-camera/lib');

const gladys = {
Expand All @@ -23,12 +22,7 @@ describe('Camera.sendCameraFileToGateway', () => {
const indexFilePath = path.join(folderPath, 'index.m3u8');
const keyfilePath = path.join(folderPath, 'index.m3u8.key');
const videoFilePath = path.join(folderPath, 'index0.ts');
const rtspCameraManager = new RtspCameraManager(
gladys,
FfmpegMock,
childProcessMock,
'de051f90-f34a-4fd5-be2e-e502339ec9bc',
);
const rtspCameraManager = new RtspCameraManager(gladys, childProcessMock, 'de051f90-f34a-4fd5-be2e-e502339ec9bc');
before(async () => {
await fse.ensureDir(folderPath);
await fse.writeFile(indexFilePath, 'this is index');
Expand Down
Loading

0 comments on commit a000b56

Please sign in to comment.