diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json
index 562d2038e3..4eb7b1a0e4 100644
--- a/front/src/config/i18n/en.json
+++ b/front/src/config/i18n/en.json
@@ -429,9 +429,12 @@
"testConnectionButton": "Test connection",
"saveButton": "Save",
"deleteButton": "Delete",
- "rotate180Label": "180° Rotation",
"saveError": "There was an error saving the camera.",
- "testConnectionError": "There was an error while getting the RTSP Flux. Are you sure the provided URL is right and accessible from Gladys instance?"
+ "testConnectionError": "There was an error while getting the RTSP Flux. Are you sure the provided URL is right and accessible from Gladys instance?",
+ "rotationO": "No rotation",
+ "rotation90": "90°",
+ "rotation18O": "180°",
+ "rotation27O": "270°"
},
"tasmota": {
"title": "Tasmota",
diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json
index 58cf3eddf7..e0b06f5031 100644
--- a/front/src/config/i18n/fr.json
+++ b/front/src/config/i18n/fr.json
@@ -555,9 +555,12 @@
"testConnectionButton": "Tester",
"saveButton": "Sauvegarder",
"deleteButton": "Supprimer",
- "rotate180Label": "Rotation 180°",
"saveError": "Une erreur s'est produite lors de l'enregistrement de la caméra.",
- "testConnectionError": "Une erreur s'est produite lors de l'obtention du flux RTSP. Êtes-vous sûr que l'URL fournie est correcte et accessible à partir de l'instance Gladys ?"
+ "testConnectionError": "Une erreur s'est produite lors de l'obtention du flux RTSP. Êtes-vous sûr que l'URL fournie est correcte et accessible à partir de l'instance Gladys ?",
+ "rotationO": "Pas de rotation",
+ "rotation90": "90°",
+ "rotation18O": "180°",
+ "rotation27O": "270°"
},
"tasmota": {
"title": "Tasmota",
diff --git a/front/src/routes/integration/all/rtsp-camera/RtspCameraBox.jsx b/front/src/routes/integration/all/rtsp-camera/RtspCameraBox.jsx
index 6ef0c7a036..0067e90cde 100644
--- a/front/src/routes/integration/all/rtsp-camera/RtspCameraBox.jsx
+++ b/front/src/routes/integration/all/rtsp-camera/RtspCameraBox.jsx
@@ -3,7 +3,7 @@ import { Component } from 'preact';
import get from 'get-value';
import cx from 'classnames';
import { RequestStatus } from '../../../../utils/consts';
-import { DEVICE_POLL_FREQUENCIES } from '../../../../../../server/utils/constants';
+import { DEVICE_POLL_FREQUENCIES, DEVICE_ROTATION } from '../../../../../../server/utils/constants';
class RtspCameraBox extends Component {
saveCamera = async () => {
@@ -69,8 +69,7 @@ class RtspCameraBox extends Component {
this.props.updateCameraUrl(this.props.cameraIndex, e.target.value);
};
updateCameraRotation = e => {
- const newValue = e.target.checked ? '1' : '0';
- this.props.updateCameraRotation(this.props.cameraIndex, newValue);
+ this.props.updateCameraRotation(this.props.cameraIndex, e.target.value);
};
updateCameraRoom = e => {
const newRoom = e.target.value === '' ? null : e.target.value;
@@ -180,20 +179,24 @@ class RtspCameraBox extends Component {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -202,7 +205,7 @@ class RtspCameraBox extends Component {
-
+
diff --git a/server/migrations/20230628144609-change-rotation-camera.js b/server/migrations/20230628144609-change-rotation-camera.js
new file mode 100644
index 0000000000..4369f71b84
--- /dev/null
+++ b/server/migrations/20230628144609-change-rotation-camera.js
@@ -0,0 +1,42 @@
+const Promise = require('bluebird');
+const db = require('../models');
+const logger = require('../utils/logger');
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ const service = await db.Service.findOne({
+ where: {
+ name: 'rtsp-camera',
+ },
+ });
+ if (service === null) {
+ return;
+ }
+ logger.info(`RstpCamera migration: Found service rtsp-camera = ${service.id}`);
+ const cameraDevices = await db.Device.findAll({
+ where: {
+ service_id: service.id,
+ },
+ });
+ logger.info(`RstpCamera migration: Found ${cameraDevices.length} devices`);
+ await Promise.each(cameraDevices, async (enedisDevice) => {
+ const deviceParam = await db.DeviceParam.findOne({
+ where: {
+ device_id: enedisDevice.id,
+ name: 'CAMERA_ROTATION',
+ },
+ });
+ if (deviceParam === null) {
+ return;
+ }
+ if (deviceParam.value === '1') {
+ logger.info(`RstpCamera migration: Updating device_params ${deviceParam.id} with 180°`);
+ deviceParam.set({
+ value: '180',
+ });
+ await deviceParam.save();
+ }
+ });
+ },
+ down: async (queryInterface, Sequelize) => {},
+};
diff --git a/server/services/rtsp-camera/lib/getImage.js b/server/services/rtsp-camera/lib/getImage.js
index 135a203fcb..45419c1665 100644
--- a/server/services/rtsp-camera/lib/getImage.js
+++ b/server/services/rtsp-camera/lib/getImage.js
@@ -2,6 +2,7 @@ const fse = require('fs-extra');
const path = require('path');
const logger = require('../../../utils/logger');
const { NotFoundError } = require('../../../utils/coreErrors');
+const { DEVICE_ROTATION } = require('../../../utils/constants');
const DEVICE_PARAM_CAMERA_URL = 'CAMERA_URL';
const DEVICE_PARAM_CAMERA_ROTATION = 'CAMERA_ROTATION';
@@ -41,12 +42,23 @@ async function getImage(device) {
const writeStream = fse.createWriteStream(filePath);
const outputOptions = [
'-vframes 1',
- '-vf scale=640:-1', // resize the image with max width = 640
'-qscale:v 15', // Effective range for JPEG is 2-31 with 31 being the worst quality.
];
- if (cameraRotationParam.value === '1') {
- outputOptions.push('-vf hflip,vflip'); // Rotate 180
+ switch (cameraRotationParam.value) {
+ case DEVICE_ROTATION.DEGREES_90:
+ outputOptions.push('-vf 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
+ break;
+ case DEVICE_ROTATION.DEGREES_270:
+ outputOptions.push('-vf scale=640:-1,transpose=2'); // Rotate 270
+ break;
+ default:
+ outputOptions.push('-vf 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 })
diff --git a/server/services/rtsp-camera/lib/startStreaming.js b/server/services/rtsp-camera/lib/startStreaming.js
index 8470c79efc..960e3e5d57 100644
--- a/server/services/rtsp-camera/lib/startStreaming.js
+++ b/server/services/rtsp-camera/lib/startStreaming.js
@@ -8,6 +8,7 @@ const util = require('util');
const randomBytes = util.promisify(require('crypto').randomBytes);
const logger = require('../../../utils/logger');
const { NotFoundError } = require('../../../utils/coreErrors');
+const { DEVICE_ROTATION } = require('../../../utils/constants');
const DEVICE_PARAM_CAMERA_URL = 'CAMERA_URL';
const DEVICE_PARAM_CAMERA_ROTATION = 'CAMERA_ROTATION';
@@ -113,8 +114,6 @@ async function startStreaming(cameraSelector, isGladysGateway, segmentDuration =
'veryfast', // Encoding presets
'-flags',
'+cgop',
- '-vf',
- 'scale=1920:-1', // Full HD resolution
'-r',
'25', // Frames (rate) per second
'-g',
@@ -150,11 +149,24 @@ async function startStreaming(cameraSelector, isGladysGateway, segmentDuration =
indexFilePath,
];
- if (cameraRotationParam.value === '1') {
- args.push('-vf'); // Rotate 180
- args.push('hflip,vflip');
+ let cameraRotationArgs = '';
+ switch (cameraRotationParam.value) {
+ case DEVICE_ROTATION.DEGREES_90:
+ cameraRotationArgs = 'scale=1920:-1,transpose=1'; // Full HD resolution & Rotate 90
+ break;
+ case DEVICE_ROTATION.DEGREES_180:
+ cameraRotationArgs = 'scale=1920:-1,transpose=1,transpose=1'; // Full HD resolution & Rotate 180
+ break;
+ case DEVICE_ROTATION.DEGREES_270:
+ cameraRotationArgs = 'scale=1920:-1,transpose=2'; // Full HD resolution & Rotate 270
+ break;
+ default:
+ cameraRotationArgs = 'scale=1920:-1'; // Full HD resolution & Rotate 0
+ break;
}
+ args.splice(8, 0, '-vf', cameraRotationArgs);
+
const options = {
timeout: 5 * 60 * 1000, // 5 minutes
};
diff --git a/server/test/services/rtsp-camera/rtspCamera.streaming.test.js b/server/test/services/rtsp-camera/rtspCamera.streaming.test.js
index 6028236862..75602bcec5 100644
--- a/server/test/services/rtsp-camera/rtspCamera.streaming.test.js
+++ b/server/test/services/rtsp-camera/rtspCamera.streaming.test.js
@@ -6,6 +6,7 @@ const { fake, assert: fakeAssert } = require('sinon');
const FfmpegMock = require('./FfmpegMock.test');
const RtspCameraManager = require('../../../services/rtsp-camera/lib');
const { NotFoundError } = require('../../../utils/coreErrors');
+const { DEVICE_ROTATION } = require('../../../utils/constants');
const device = {
id: 'a6fb4cb8-ccc2-4234-a752-b25d1eb5ab6b',
@@ -133,7 +134,7 @@ describe('Camera.streaming', () => {
await rtspCameraManager.stopStreaming('my-camera');
fakeAssert.called(rtspCameraManager.sendCameraFileToGatewayLimited);
});
- it('should star with rotation & stop streaming', async () => {
+ it('should star with 90 rotation & stop streaming', async () => {
const gladysDeviceWithRotation = {
config: {
tempFolder: '/tmp/gladys',
@@ -149,7 +150,79 @@ describe('Camera.streaming', () => {
},
{
name: 'CAMERA_ROTATION',
- value: '1',
+ value: DEVICE_ROTATION.DEGREES_90,
+ },
+ ],
+ }),
+ },
+ };
+ rtspCameraManager = new RtspCameraManager(
+ gladysDeviceWithRotation,
+ FfmpegMock,
+ childProcessMock,
+ 'de051f90-f34a-4fd5-be2e-e502339ec9bc',
+ );
+ rtspCameraManager.onNewCameraFile = fake.resolves(null);
+ const liveStreamingProcess = await rtspCameraManager.startStreaming('my-camera', false, 1);
+ expect(liveStreamingProcess).to.have.property('camera_folder');
+ expect(liveStreamingProcess).to.have.property('encryption_key');
+ await rtspCameraManager.liveActivePing('my-camera');
+ await rtspCameraManager.stopStreaming('my-camera');
+ fakeAssert.called(rtspCameraManager.onNewCameraFile);
+ });
+ it('should star with 180 rotation & stop streaming', async () => {
+ const gladysDeviceWithRotation = {
+ config: {
+ tempFolder: '/tmp/gladys',
+ },
+ device: {
+ getBySelector: fake.resolves({
+ id: 'a6fb4cb8-ccc2-4234-a752-b25d1eb5ab6b',
+ selector: 'my-camera',
+ params: [
+ {
+ name: 'CAMERA_URL',
+ value: 'test',
+ },
+ {
+ name: 'CAMERA_ROTATION',
+ value: DEVICE_ROTATION.DEGREES_180,
+ },
+ ],
+ }),
+ },
+ };
+ rtspCameraManager = new RtspCameraManager(
+ gladysDeviceWithRotation,
+ FfmpegMock,
+ childProcessMock,
+ 'de051f90-f34a-4fd5-be2e-e502339ec9bc',
+ );
+ rtspCameraManager.onNewCameraFile = fake.resolves(null);
+ const liveStreamingProcess = await rtspCameraManager.startStreaming('my-camera', false, 1);
+ expect(liveStreamingProcess).to.have.property('camera_folder');
+ expect(liveStreamingProcess).to.have.property('encryption_key');
+ await rtspCameraManager.liveActivePing('my-camera');
+ await rtspCameraManager.stopStreaming('my-camera');
+ fakeAssert.called(rtspCameraManager.onNewCameraFile);
+ });
+ it('should star with 270 rotation & stop streaming', async () => {
+ const gladysDeviceWithRotation = {
+ config: {
+ tempFolder: '/tmp/gladys',
+ },
+ device: {
+ getBySelector: fake.resolves({
+ id: 'a6fb4cb8-ccc2-4234-a752-b25d1eb5ab6b',
+ selector: 'my-camera',
+ params: [
+ {
+ name: 'CAMERA_URL',
+ value: 'test',
+ },
+ {
+ name: 'CAMERA_ROTATION',
+ value: DEVICE_ROTATION.DEGREES_270,
},
],
}),
diff --git a/server/test/services/rtsp-camera/rtspCamera.test.js b/server/test/services/rtsp-camera/rtspCamera.test.js
index 779f76b265..20fc277df3 100644
--- a/server/test/services/rtsp-camera/rtspCamera.test.js
+++ b/server/test/services/rtsp-camera/rtspCamera.test.js
@@ -33,7 +33,7 @@ const gladys = {
},
};
-const deviceFlipped = {
+const deviceRotation90 = {
id: 'a6fb4cb8-ccc2-4234-a752-b25d1eb5ab6c',
selector: 'my-camera',
params: [
@@ -43,7 +43,37 @@ const deviceFlipped = {
},
{
name: 'CAMERA_ROTATION',
- value: '1',
+ value: '90',
+ },
+ ],
+};
+
+const deviceRotation180 = {
+ id: 'a6fb4cb8-ccc2-4234-a752-b25d1eb5ab6c',
+ selector: 'my-camera',
+ params: [
+ {
+ name: 'CAMERA_URL',
+ value: 'test',
+ },
+ {
+ name: 'CAMERA_ROTATION',
+ value: '180',
+ },
+ ],
+};
+
+const deviceRotation270 = {
+ id: 'a6fb4cb8-ccc2-4234-a752-b25d1eb5ab6c',
+ selector: 'my-camera',
+ params: [
+ {
+ name: 'CAMERA_URL',
+ value: 'test',
+ },
+ {
+ name: 'CAMERA_ROTATION',
+ value: '270',
},
],
};
@@ -107,8 +137,16 @@ describe('RtspCameraManager commands', () => {
const image = await rtspCameraManager.getImage(device);
expect(image).to.equal('image/png;base64,aW1hZ2U=');
});
+ it('should getImage 90°', async () => {
+ const image = await rtspCameraManager.getImage(deviceRotation90);
+ expect(image).to.equal('image/png;base64,aW1hZ2U=');
+ });
it('should getImage 180°', async () => {
- const image = await rtspCameraManager.getImage(deviceFlipped);
+ const image = await rtspCameraManager.getImage(deviceRotation180);
+ expect(image).to.equal('image/png;base64,aW1hZ2U=');
+ });
+ it('should getImage 270°', async () => {
+ const image = await rtspCameraManager.getImage(deviceRotation270);
expect(image).to.equal('image/png;base64,aW1hZ2U=');
});
it('should return error', async () => {
diff --git a/server/utils/constants.js b/server/utils/constants.js
index 0b19e3ad64..962d7c003c 100644
--- a/server/utils/constants.js
+++ b/server/utils/constants.js
@@ -762,6 +762,13 @@ const DEVICE_POLL_FREQUENCIES = {
EVERY_SECONDS: 1 * 1000,
};
+const DEVICE_ROTATION = {
+ DEGREES_0: '0',
+ DEGREES_90: '90',
+ DEGREES_180: '180',
+ DEGREES_270: '270',
+};
+
const WEBSOCKET_MESSAGE_TYPES = {
BACKUP: {
DOWNLOADED: 'backup.downloaded',
@@ -962,6 +969,8 @@ module.exports.SESSION_TOKEN_TYPE_LIST = SESSION_TOKEN_TYPE_LIST;
module.exports.DEVICE_POLL_FREQUENCIES = DEVICE_POLL_FREQUENCIES;
module.exports.DEVICE_POLL_FREQUENCIES_LIST = createList(DEVICE_POLL_FREQUENCIES);
+module.exports.DEVICE_ROTATION = DEVICE_ROTATION;
+
module.exports.WEBSOCKET_MESSAGE_TYPES = WEBSOCKET_MESSAGE_TYPES;
module.exports.DEVICE_FEATURE_UNITS = DEVICE_FEATURE_UNITS;