12 Commits
dev ... dev3

Author SHA1 Message Date
efaa49912e Passage DB en Prod 2025-02-11 22:53:05 +01:00
bd9a9b70a1 Merge pull request 'Ajout des fonctionnalités de traitement vidéo' (#2) from dev2 into main
Reviewed-on: https://gitea.kerboul.me/timelapse/timelapse-backend/pulls/2
2025-02-11 21:51:35 +00:00
cbb18e0ca2 Réorganisation du code dans videoManager.js et activation des tests de gestion des dossiers dans tester.js 2025-02-11 22:49:37 +01:00
a80193dadc Ajout de la gestion des chemins d'images et amélioration des messages de log dans plusieurs modules 2025-02-11 22:40:32 +01:00
33b55e0dc0 Tri des images numériquement avant la création de la vidéo et mise à jour du fichier temporaire 2025-02-11 19:29:03 +01:00
f56c35c5f9 Migration de la fonction de création de vidéo vers un nouveau module et suppression de l'ancienne implémentation 2025-02-11 19:26:42 +01:00
66d51f24d9 Mise à jour de la documentation des routes d'upload pour utiliser requestBody au lieu de formData 2025-02-11 18:34:47 +01:00
042ea5cc50 Suppression du fichier Swagger pour le schéma de mesure et mise à jour des descriptions dans les routes d'images et de projets 2025-02-11 18:30:32 +01:00
08fa489f4c Ajout de la documentation Swagger pour le schéma de mesure 2025-02-11 18:23:25 +01:00
83b7f14778 Modification de la configuration de connexion à la base de données pour passer en mode production 2025-02-11 18:19:02 +01:00
3f34fdeef3 Ajout de package-lock.json au fichier .gitignore 2025-02-11 18:17:21 +01:00
4df3dae224 Merge pull request 'Réorganisation des routes API et ajout de la gestion des téléchargements d'images' (#1) from dev into main
Reviewed-on: https://gitea.kerboul.me/timelapse/timelapse-backend/pulls/1
2025-02-11 17:15:29 +00:00
15 changed files with 578 additions and 330 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
node_modules/ node_modules/
info.log info.log
storage/ storage/
uploads/ uploads/
package-lock.json

4
db.js
View File

@@ -1,6 +1,6 @@
const { Client } = require('pg'); const { Client } = require('pg');
const local = true; const local = false;
// Connexion à la base de données PostgreSQL // Connexion à la base de données PostgreSQL
const client = new Client({ const client = new Client({
host: local ? 'mikoshi' : '172.30.0.2', host: local ? 'mikoshi' : '172.30.0.2',
@@ -16,7 +16,7 @@ function connectWithRetry() {
console.error('Erreur de connexion à la base de données:', err); console.error('Erreur de connexion à la base de données:', err);
setTimeout(connectWithRetry, 30000); // Réessayer après 30 secondes setTimeout(connectWithRetry, 30000); // Réessayer après 30 secondes
} else { } else {
console.log('Connecté à la base de données PostgreSQL.'); console.log('[DB] Connecté à la base de données PostgreSQL.');
} }
}); });
} }

View File

@@ -13,7 +13,7 @@ const serverError = require('../utils/serverError');
* summary: Retrieve a smile image * summary: Retrieve a smile image
* responses: * responses:
* 200: * 200:
* description: Smile image retrieved successfully * description: A smile image
* content: * content:
* image/jpeg: * image/jpeg:
* schema: * schema:
@@ -23,67 +23,65 @@ const serverError = require('../utils/serverError');
* description: Image not found * description: Image not found
*/ */
router.get('/smile', (req, res) => { router.get('/smile', (req, res) => {
const imagePath = dbTester.getSmileImage(); const imagePath = dbTester.getSmileImage();
fs.access(imagePath, fs.constants.F_OK, (err) => { fs.access(imagePath, fs.constants.F_OK, (err) => {
if (err) { if (err) {
console.error('Image not found:', err); console.error('Image not found:', err);
return res.status(404).json({ error: 'Image not found' }); return res.status(404).json({ error: 'Image not found' });
} }
res.sendFile(imagePath); res.sendFile(imagePath);
}); });
}); });
/** /**
* @swagger * @swagger
* /images/{projectId}/{orderId}: * /images/{projectId}/{orderId}:
* get: * get:
* summary: Retrieve an image by project ID and order ID * summary: Retrieve an image by project and order ID
* parameters: * parameters:
* - in: path * - in: path
* name: projectId * name: projectId
* required: true * required: true
* schema: * schema:
* type: string * type: string
* description: The ID of the project * description: The project ID
* - in: path * - in: path
* name: orderId * name: orderId
* required: true * required: true
* schema: * schema:
* type: string * type: string
* description: The ID of the order * description: The order ID
* responses: * responses:
* 200: * 200:
* description: Image retrieved successfully * description: An image file
* content: * content:
* image/jpeg: * application/octet-stream:
* schema: * schema:
* type: string * type: string
* format: binary * format: binary
* 404: * 404:
* description: Image not found * description: Image not found
* 500:
* description: Internal server error
*/ */
router.get('/images/:projectId/:orderId', (req, res) => { router.get('/images/:projectId/:orderId', (req, res) => {
const projectId = req.params.projectId; const projectId = req.params.projectId;
const orderId = req.params.orderId; const orderId = req.params.orderId;
const query = 'SELECT path FROM public.measurements WHERE project_id = $1 AND order_id = $2'; const query = 'SELECT path FROM public.measurements WHERE project_id = $1 AND order_id = $2';
db.query(query, [projectId, orderId], (err, results) => { db.query(query, [projectId, orderId], (err, results) => {
if (err) {
return serverError.sendError('Error getting image:', res, err);
}
if (results.rows.length === 0) {
return res.status(404).json({ error: 'Image not found' });
}
const imagePath = results.rows[0].path;
fs.access(imagePath, fs.constants.F_OK, (err) => {
if (err) { if (err) {
console.error('Image not found:', err); return serverError.sendError('Error getting image:', res, err);
return res.status(404).json({ error: 'Image not found' });
} }
res.download(imagePath); if (results.rows.length === 0) {
}); return res.status(404).json({ error: 'Image not found' });
}); }
const imagePath = results.rows[0].path;
fs.access(imagePath, fs.constants.F_OK, (err) => {
if (err) {
console.error('Image not found:', err);
return res.status(404).json({ error: 'Image not found' });
}
res.download(imagePath);
});
});
}); });
module.exports = router; module.exports = router;

View File

@@ -18,18 +18,32 @@ const serverError = require('../utils/serverError');
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: '#/components/schemas/Measurement' * type: object
* properties:
* id:
* type: integer
* project_id:
* type: integer
* timestamp:
* type: string
* format: date-time
* image_path:
* type: string
* temperature:
* type: number
* humidity:
* type: number
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/measurements', (req, res) => { router.get('/measurements', (req, res) => {
const query = 'SELECT * FROM public.measurements'; const query = 'SELECT * FROM public.measurements';
db.query(query, (err, results) => { db.query(query, (err, results) => {
if (err) { if (err) {
serverError.sendError('Erreur lors de la récupération des mesures:', res, err); serverError.sendError('Erreur lors de la récupération des mesures:', res, err);
} }
res.json(results.rows); res.json(results.rows);
}); });
}); });
/** /**
@@ -51,24 +65,38 @@ router.get('/measurements', (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: '#/components/schemas/Measurement' * type: object
* properties:
* id:
* type: integer
* project_id:
* type: integer
* timestamp:
* type: string
* format: date-time
* image_path:
* type: string
* temperature:
* type: number
* humidity:
* type: number
* 400: * 400:
* description: ID de mesure invalide. * description: ID de mesure invalide.
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/measurements/:id', (req, res) => { router.get('/measurements/:id', (req, res) => {
const measurementId = req.params.id; const measurementId = req.params.id;
if (!measurementId || isNaN(measurementId)) { if (!measurementId || isNaN(measurementId)) {
return res.status(400).json({ error: 'Invalid measurement ID' }); return res.status(400).json({ error: 'Invalid measurement ID' });
} }
const query = 'SELECT * FROM public.measurements WHERE id = $1'; const query = 'SELECT * FROM public.measurements WHERE id = $1';
db.query(query, [measurementId], (err, results) => { db.query(query, [measurementId], (err, results) => {
if (err) { if (err) {
serverError.sendError('Erreur lors de la récupération de la mesure:', res, err); serverError.sendError('Erreur lors de la récupération de la mesure:', res, err);
} }
res.json(results.rows); res.json(results.rows);
}); });
}); });
/** /**
@@ -96,24 +124,38 @@ router.get('/measurements/:id', (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: '#/components/schemas/Measurement' * type: object
* properties:
* id:
* type: integer
* project_id:
* type: integer
* timestamp:
* type: string
* format: date-time
* image_path:
* type: string
* temperature:
* type: number
* humidity:
* type: number
* 400: * 400:
* description: ID de projet ou de commande invalide. * description: ID de projet ou de commande invalide.
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/measurements/:projectId/:orderId', async (req, res) => { router.get('/measurements/:projectId/:orderId', async (req, res) => {
const projectId = req.params.projectId; const projectId = req.params.projectId;
const orderId = req.params.orderId; const orderId = req.params.orderId;
if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) { if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) {
return res.status(400).json({ error: 'Invalid project ID or order ID' }); return res.status(400).json({ error: 'Invalid project ID or order ID' });
} }
try { try {
const measurement = await measureManager.getMeasurement(projectId, orderId); const measurement = await measureManager.getMeasurement(projectId, orderId);
res.json(measurement); res.json(measurement);
} catch (error) { } catch (error) {
serverError.sendError('Error getting measurement:', res, error); serverError.sendError('Error getting measurement:', res, error);
} }
}); });
/** /**
@@ -149,17 +191,17 @@ router.get('/measurements/:projectId/:orderId', async (req, res) => {
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.post('/measurements', (req, res) => { router.post('/measurements', (req, res) => {
const { project_id, timestamp, image_path, temperature, humidity } = req.body; const { project_id, timestamp, image_path, temperature, humidity } = req.body;
if (!project_id || !timestamp || !image_path || !temperature || !humidity) { if (!project_id || !timestamp || !image_path || !temperature || !humidity) {
return res.status(400).json({ error: 'All fields are required' }); return res.status(400).json({ error: 'All fields are required' });
} }
const query = 'INSERT INTO public.measurements (project_id, timestamp, image_path, temperature, humidity) VALUES ($1, $2, $3, $4, $5) RETURNING id'; const query = 'INSERT INTO public.measurements (project_id, timestamp, image_path, temperature, humidity) VALUES ($1, $2, $3, $4, $5) RETURNING id';
db.query(query, [project_id, timestamp, image_path, temperature, humidity], (err, results) => { db.query(query, [project_id, timestamp, image_path, temperature, humidity], (err, results) => {
if (err) { if (err) {
serverError.sendError('Erreur lors de l\'ajout de la mesure:', res, err); serverError.sendError('Erreur lors de l\'ajout de la mesure:', res, err);
} }
res.status(201).json({ message: 'Mesure ajoutée avec succès', id: results.rows[0].id }); res.status(201).json({ message: 'Mesure ajoutée avec succès', id: results.rows[0].id });
}); });
}); });
/** /**
@@ -184,16 +226,16 @@ router.post('/measurements', (req, res) => {
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.delete('/measurements/:id', async (req, res) => { router.delete('/measurements/:id', async (req, res) => {
const measurementId = req.params.id; const measurementId = req.params.id;
if (!measurementId || isNaN(measurementId)) { if (!measurementId || isNaN(measurementId)) {
return res.status(400).json({ error: 'Invalid measurement ID' }); return res.status(400).json({ error: 'Invalid measurement ID' });
} }
try { try {
const measurement = await measureManager.deleteMeasurement(measurementId); const measurement = await measureManager.deleteMeasurement(measurementId);
res.status(200).json({ message: 'Measurement deleted successfully', id: measurementId }); res.status(200).json({ message: 'Measurement deleted successfully', id: measurementId });
} catch (error) { } catch (error) {
serverError.sendError('Error deleting measurement:', res, error); serverError.sendError('Error deleting measurement:', res, error);
} }
}); });
/** /**
@@ -224,17 +266,17 @@ router.delete('/measurements/:id', async (req, res) => {
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.delete('/measurements/:projectId/:orderId', async (req, res) => { router.delete('/measurements/:projectId/:orderId', async (req, res) => {
const projectId = req.params.projectId; const projectId = req.params.projectId;
const orderId = req.params.orderId; const orderId = req.params.orderId;
if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) { if (!projectId || isNaN(projectId) || !orderId || isNaN(orderId)) {
return res.status(400).json({ error: 'Invalid project ID or order ID' }); return res.status(400).json({ error: 'Invalid project ID or order ID' });
} }
try { try {
const measurement = await measureManager.deleteMeasurementByOrderId(projectId, orderId); const measurement = await measureManager.deleteMeasurementByOrderId(projectId, orderId);
res.status(200).json({ message: 'Measurement deleted successfully', id: measurement.id }); res.status(200).json({ message: 'Measurement deleted successfully', id: measurement.id });
} catch (error) { } catch (error) {
serverError.sendError('Error deleting measurement:', res, error); serverError.sendError('Error deleting measurement:', res, error);
} }
}); });
module.exports = router; module.exports = router;

View File

@@ -17,17 +17,17 @@ const serverError = require('../utils/serverError');
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: '#/components/schemas/Project' * type: object
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/projects', async (req, res) => { router.get('/projects', async (req, res) => {
try { try {
const projects = await projectManager.getAllProjects(); const projects = await projectManager.getAllProjects();
res.json(projects); res.json(projects);
} catch (error) { } catch (error) {
serverError.sendError('Error getting all projects:', res, error); serverError.sendError('Error getting all projects:', res, error);
} }
}); });
/** /**
@@ -49,23 +49,23 @@ router.get('/projects', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: '#/components/schemas/Project' * type: object
* 400: * 400:
* description: ID de projet invalide. * description: ID de projet invalide.
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/projects/:id', async (req, res) => { router.get('/projects/:id', async (req, res) => {
const projectId = req.params.id; const projectId = req.params.id;
if (!projectId || isNaN(projectId)) { if (!projectId || isNaN(projectId)) {
return res.status(400).json({ error: 'Invalid project ID' }); return res.status(400).json({ error: 'Invalid project ID' });
} }
try { try {
const project = await projectManager.getProjectById(projectId); const project = await projectManager.getProjectById(projectId);
res.json(project); res.json(project);
} catch (error) { } catch (error) {
serverError.sendError('Error getting project by ID:', res, error); serverError.sendError('Error getting project by ID:', res, error);
} }
}); });
/** /**
@@ -89,23 +89,23 @@ router.get('/projects/:id', async (req, res) => {
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: '#/components/schemas/Video' * type: object
* 400: * 400:
* description: ID de projet invalide. * description: ID de projet invalide.
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/projects/:id/videos', async (req, res) => { router.get('/projects/:id/videos', async (req, res) => {
const projectId = req.params.id; const projectId = req.params.id;
if (!projectId || isNaN(projectId)) { if (!projectId || isNaN(projectId)) {
return res.status(400).json({ error: 'Invalid project ID' }); return res.status(400).json({ error: 'Invalid project ID' });
} }
try { try {
const videos = await projectManager.getVideosByProjectId(projectId); const videos = await projectManager.getVideosByProjectId(projectId);
res.json(videos); res.json(videos);
} catch (error) { } catch (error) {
serverError.sendError('Error getting videos by project ID:', res, error); serverError.sendError('Error getting videos by project ID:', res, error);
} }
}); });
/** /**
@@ -129,23 +129,23 @@ router.get('/projects/:id/videos', async (req, res) => {
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: '#/components/schemas/Measurement' * type: object
* 400: * 400:
* description: ID de projet invalide. * description: ID de projet invalide.
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/projects/:id/measurements', async (req, res) => { router.get('/projects/:id/measurements', async (req, res) => {
const projectId = req.params.id; const projectId = req.params.id;
if (!projectId || isNaN(projectId)) { if (!projectId || isNaN(projectId)) {
return res.status(400).json({ error: 'Invalid project ID' }); return res.status(400).json({ error: 'Invalid project ID' });
} }
try { try {
const measurements = await projectManager.getMeasurementsByProjectId(projectId); const measurements = await projectManager.getMeasurementsByProjectId(projectId);
res.json(measurements); res.json(measurements);
} catch (error) { } catch (error) {
serverError.sendError('Error getting measurements by project ID:', res, error); serverError.sendError('Error getting measurements by project ID:', res, error);
} }
}); });
/** /**
@@ -174,17 +174,17 @@ router.get('/projects/:id/measurements', async (req, res) => {
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.post('/projects', async (req, res) => { router.post('/projects', async (req, res) => {
const { name, description } = req.body; const { name, description } = req.body;
if (!name || !description) { if (!name || !description) {
return res.status(400).json({ error: 'Name and description are required' }); return res.status(400).json({ error: 'Name and description are required' });
} }
try { try {
const project = await projectManager.createProject(name, description, new Date(), 0); const project = await projectManager.createProject(name, description, new Date(), 0);
projectManager.createProjectDirectory(project.id); projectManager.createProjectDirectory(project.id);
res.status(201).json({ message: 'Project added successfully', id: project.id }); res.status(201).json({ message: 'Project added successfully', id: project.id });
} catch (error) { } catch (error) {
serverError.sendError('Error creating project:', res, error); serverError.sendError('Error creating project:', res, error);
} }
}); });
/** /**
@@ -209,17 +209,17 @@ router.post('/projects', async (req, res) => {
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.delete('/projects/:id', async (req, res) => { router.delete('/projects/:id', async (req, res) => {
const projectId = req.params.id; const projectId = req.params.id;
if (!projectId || isNaN(projectId)) { if (!projectId || isNaN(projectId)) {
return res.status(400).json({ error: 'Invalid project ID' }); return res.status(400).json({ error: 'Invalid project ID' });
} }
try { try {
projectManager.deleteProjectDirectory(projectId); projectManager.deleteProjectDirectory(projectId);
projectManager.deleteProjectById(projectId); projectManager.deleteProjectById(projectId);
res.status(200).json({ message: 'Project deleted successfully', id: projectId }); res.status(200).json({ message: 'Project deleted successfully', id: projectId });
} catch (error) { } catch (error) {
serverError.sendError('Error deleting project:', res, error); serverError.sendError('Error deleting project:', res, error);
} }
}); });
module.exports = router; module.exports = router;

View File

@@ -12,21 +12,22 @@ const upload = multer({ storage: multer.memoryStorage() });
* post: * post:
* summary: Télécharger une image * summary: Télécharger une image
* description: Télécharge une image pour un projet et un ordre spécifiques. * description: Télécharge une image pour un projet et un ordre spécifiques.
* consumes: * requestBody:
* - multipart/form-data * content:
* parameters: * multipart/form-data:
* - in: formData * schema:
* name: image * type: object
* type: file * properties:
* description: Fichier image à télécharger * image:
* - in: formData * type: string
* name: projectId * format: binary
* type: integer * description: Fichier image à télécharger
* description: ID du projet * projectId:
* - in: formData * type: integer
* name: orderId * description: ID du projet
* type: integer * orderId:
* description: ID de la commande * type: integer
* description: ID de la commande
* responses: * responses:
* 200: * 200:
* description: Image téléchargée avec succès. * description: Image téléchargée avec succès.
@@ -66,30 +67,29 @@ router.post('/upload', upload.single('image'), async (req, res) => {
* post: * post:
* summary: Télécharger une mesure avec une image * summary: Télécharger une mesure avec une image
* description: Télécharge une mesure avec une image pour un projet spécifique. * description: Télécharge une mesure avec une image pour un projet spécifique.
* consumes: * requestBody:
* - multipart/form-data * content:
* parameters: * multipart/form-data:
* - in: formData * schema:
* name: image * type: object
* type: file * properties:
* description: Fichier image à télécharger * image:
* - in: formData * type: string
* name: projectId * format: binary
* type: integer * description: Fichier image à télécharger
* description: ID du projet * projectId:
* - in: formData * type: integer
* name: timestamp * description: ID du projet
* type: string * timestamp:
* format: date-time * type: string
* description: Horodatage de la mesure * format: date-time
* - in: formData * description: Horodatage de la mesure
* name: temperature * temperature:
* type: number * type: number
* description: Température mesurée * description: Température mesurée
* - in: formData * humidity:
* name: humidity * type: number
* type: number * description: Humidité mesurée
* description: Humidité mesurée
* responses: * responses:
* 200: * 200:
* description: Mesure téléchargée avec succès. * description: Mesure téléchargée avec succès.

View File

@@ -2,6 +2,7 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const db = require('../db'); const db = require('../db');
const serverError = require('../utils/serverError'); const serverError = require('../utils/serverError');
const videoManager = require('../src/video/videoManager');
/** /**
* @swagger * @swagger
@@ -17,18 +18,43 @@ const serverError = require('../utils/serverError');
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: '#/components/schemas/Video' * type: object
* properties:
* id:
* type: integer
* project_id:
* type: integer
* measurement_ids:
* type: string
* video_path:
* type: string
* start_timestamp:
* type: string
* end_timestamp:
* type: string
* image_count:
* type: integer
* resolution:
* type: string
* duration:
* type: number
* fps:
* type: number
* status:
* type: integer
* name:
* type: string
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/videos', (req, res) => { router.get('/videos', (req, res) => {
const query = 'SELECT * FROM public.videos'; const query = 'SELECT * FROM public.videos';
db.query(query, (err, results) => { db.query(query, (err, results) => {
if (err) { if (err) {
serverError.sendError('Erreur lors de la récupération des vidéos:', res, err); serverError.sendError('Erreur lors de la récupération des vidéos:', res, err);
} }
res.json(results.rows); res.json(results.rows);
}); });
}); });
/** /**
@@ -50,24 +76,49 @@ router.get('/videos', (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: '#/components/schemas/Video' * type: object
* properties:
* id:
* type: integer
* project_id:
* type: integer
* measurement_ids:
* type: string
* video_path:
* type: string
* start_timestamp:
* type: string
* end_timestamp:
* type: string
* image_count:
* type: integer
* resolution:
* type: string
* duration:
* type: number
* fps:
* type: number
* status:
* type: integer
* name:
* type: string
* 400: * 400:
* description: ID de vidéo invalide. * description: ID de vidéo invalide.
* 500: * 500:
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.get('/videos/:id', (req, res) => { router.get('/videos/:id', (req, res) => {
const videoId = req.params.id; const videoId = req.params.id;
if (!videoId || isNaN(videoId)) { if (!videoId || isNaN(videoId)) {
return res.status(400).json({ error: 'Invalid video ID' }); return res.status(400).json({ error: 'Invalid video ID' });
} }
const query = 'SELECT * FROM public.videos WHERE id = $1'; const query = 'SELECT * FROM public.videos WHERE id = $1';
db.query(query, [videoId], (err, results) => { db.query(query, [videoId], (err, results) => {
if (err) { if (err) {
serverError.sendError('Erreur lors de la récupération de la vidéo:', res, err); serverError.sendError('Erreur lors de la récupération de la vidéo:', res, err);
} }
res.json(results.rows); res.json(results.rows);
}); });
}); });
/** /**
@@ -104,40 +155,40 @@ router.get('/videos/:id', (req, res) => {
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.post('/videos', (req, res) => { router.post('/videos', (req, res) => {
const { project_id, measurement_ids, video_path, duration, resolution, name } = req.body; const { project_id, measurement_ids, video_path, duration, resolution, name } = req.body;
if (!project_id || !measurement_ids || !video_path || !duration || !resolution || !name) { if (!project_id || !measurement_ids || !video_path || !duration || !resolution || !name) {
return res.status(400).json({ error: 'All fields are required' }); return res.status(400).json({ error: 'All fields are required' });
} }
const list_ids = measurement_ids.split(','); const list_ids = measurement_ids.split(',');
const image_count = list_ids.length; const image_count = list_ids.length;
const videoPath = '/videos/' + name + '.mp4'; const videoPath = '/videos/' + name + '.mp4';
const query_first = 'SELECT timestamp FROM public.measurements WHERE id = $1'; const query_first = 'SELECT timestamp FROM public.measurements WHERE id = $1';
const query_last = 'SELECT timestamp FROM public.measurements WHERE id = $1'; const query_last = 'SELECT timestamp FROM public.measurements WHERE id = $1';
db.query(query_first, [list_ids[0]], (err, results) => { db.query(query_first, [list_ids[0]], (err, results) => {
if (err) {
serverError.sendError('Erreur lors de la récupération du timestamp de la première image:', res, err);
}
const start_timestamp = results.rows[0].timestamp;
db.query(query_last, [list_ids[image_count - 1]], (err, results) => {
if (err) { if (err) {
serverError.sendError('Erreur lors de la récupération du timestamp de la dernière image:', res, err); serverError.sendError('Erreur lors de la récupération du timestamp de la première image:', res, err);
} }
const end_timestamp = results.rows[0].timestamp; const start_timestamp = results.rows[0].timestamp;
const fps = image_count / duration;
const query = 'INSERT INTO public.videos (project_id, measurement_ids, video_path, start_timestamp, end_timestamp, image_count, resolution, duration, fps, status, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id'; db.query(query_last, [list_ids[image_count - 1]], (err, results) => {
db.query(query, [project_id, measurement_ids, videoPath, start_timestamp, end_timestamp, image_count, resolution, duration, fps, 0, name], (err, results) => { if (err) {
if (err) { serverError.sendError('Erreur lors de la récupération du timestamp de la dernière image:', res, err);
serverError.sendError('Erreur lors de l\'ajout de la vidéo:', res, err); }
} const end_timestamp = results.rows[0].timestamp;
res.status(201).json({ message: 'Vidéo ajoutée avec succès', id: results.rows[0].id }); const fps = image_count / duration;
const query = 'INSERT INTO public.videos (project_id, measurement_ids, video_path, start_timestamp, end_timestamp, image_count, resolution, duration, fps, status, name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id';
db.query(query, [project_id, measurement_ids, videoPath, start_timestamp, end_timestamp, image_count, resolution, duration, fps, 0, name], (err, results) => {
if (err) {
serverError.sendError('Erreur lors de l\'ajout de la vidéo:', res, err);
}
res.status(201).json({ message: 'Vidéo ajoutée avec succès', id: results.rows[0].id });
});
}); });
}); });
});
}); });
/** /**
@@ -164,20 +215,20 @@ router.post('/videos', (req, res) => {
* description: Erreur serveur. * description: Erreur serveur.
*/ */
router.delete('/videos/:id', (req, res) => { router.delete('/videos/:id', (req, res) => {
const videoId = req.params.id; const videoId = req.params.id;
if (!videoId || isNaN(videoId)) { if (!videoId || isNaN(videoId)) {
return res.status(400).json({ error: 'Invalid video ID' }); return res.status(400).json({ error: 'Invalid video ID' });
} }
const query = 'DELETE FROM public.videos WHERE id = $1 RETURNING id'; const query = 'DELETE FROM public.videos WHERE id = $1 RETURNING id';
db.query(query, [videoId], (err, results) => { db.query(query, [videoId], (err, results) => {
if (err) { if (err) {
serverError.sendError('Erreur lors de la suppression de la vidéo:', res, err); serverError.sendError('Erreur lors de la suppression de la vidéo:', res, err);
} }
if (results.rowCount === 0) { if (results.rowCount === 0) {
return res.status(404).json({ error: 'Aucune vidéo trouvée avec cet ID.' }); return res.status(404).json({ error: 'Aucune vidéo trouvée avec cet ID.' });
} }
res.status(200).json({ message: 'Vidéo supprimée avec succès', id: videoId }); res.status(200).json({ message: 'Vidéo supprimée avec succès', id: videoId });
}); });
}); });
module.exports = router; module.exports = router;

View File

@@ -60,6 +60,6 @@ app.get('/', (req, res) => {
// Démarrer le serveur // Démarrer le serveur
app.listen(port, () => { app.listen(port, () => {
console.log(`Serveur démarré sur http://localhost:${port}`); console.log(`[SERVER] Serveur démarré sur http://localhost:${port}`);
console.log(`Swagger documentation disponible sur http://localhost:${port}/api-docs`); console.log(`[SERVER] Swagger documentation disponible sur http://localhost:${port}/api-docs`);
}); });

View File

@@ -3,17 +3,20 @@ import path from 'path';
import storageManager from '../data/storageManager.js'; import storageManager from '../data/storageManager.js';
import fs from 'fs'; import fs from 'fs';
let localCounter = 0;
async function checkAndRemoveInvalidEntries() { async function checkAndRemoveInvalidEntries() {
console.log('Checking for invalid entries...'); localCounter = 0;
console.log('[INFO] Vérification et suppression des entrées invalides...');
try { try {
const measurementsRes = await db.query('SELECT id, path FROM measurements'); const measurementsRes = await db.query('SELECT id, path FROM measurements');
//console.log('Fetched measurements:', measurementsRes.rows); //console.log('Fetched measurements:', measurementsRes.rows);
for (const row of measurementsRes.rows) { for (const row of measurementsRes.rows) {
//console.log('Checking file path:', row.path); //console.log('Checking file path:', row.path);
if (!fs.existsSync(row.path)) { if (!fs.existsSync(row.path)) {
// Remove invalid entry
await db.query('DELETE FROM measurements WHERE id = $1', [row.id]); await db.query('DELETE FROM measurements WHERE id = $1', [row.id]);
console.log(`Deleted invalid measurement entry with id: ${row.id}`); console.log(`Deleted invalid measurement entry with id: ${row.id}`);
localCounter++;
} }
} }
@@ -26,8 +29,14 @@ async function checkAndRemoveInvalidEntries() {
// Remove the file if the entry does not exist // Remove the file if the entry does not exist
fs.unlinkSync(imagePath); fs.unlinkSync(imagePath);
console.log(`Deleted file at path: ${imagePath} as its database entry does not exist`); console.log(`Deleted file at path: ${imagePath} as its database entry does not exist`);
localCounter++; // Increment counter if entry is deleted
} }
} }
if (localCounter > 0) {
console.log(`[INFO] ${localCounter} entrées ont été modifiées`);
} else {
console.log('[INFO] Aucune entrée n\'a été modifiée.');
}
} catch (err) { } catch (err) {
console.error('Error checking and removing invalid entries:', err); console.error('Error checking and removing invalid entries:', err);
} }
@@ -36,7 +45,8 @@ async function checkAndRemoveInvalidEntries() {
// Run the check periodically // Run the check periodically
setInterval(checkAndRemoveInvalidEntries, 10000); // Every second console.log('[INFO] Activation du FileWatcher pour surveiller les fichiers invalides...')
setInterval(checkAndRemoveInvalidEntries, 10000); // Every 10 seconds
// Initial run // Initial run
checkAndRemoveInvalidEntries(); checkAndRemoveInvalidEntries();

View File

@@ -1,64 +1,80 @@
const fs = require('fs'); const fs = require('fs').promises;
const path = require('path'); const path = require('path');
const PROJECTS_DIR = path.join('.'); const PROJECTS_DIR = path.join('.');
function createFolder(name){ async function createFolder(name) {
const projectDir = path.join(PROJECTS_DIR, `${name}`); const projectDir = path.join(PROJECTS_DIR, `${name}`);
if (!fs.existsSync(projectDir)) { try {
fs.mkdirSync(projectDir, { recursive: true }); await fs.access(projectDir);
} catch (error) {
if (error.code === 'ENOENT') {
await fs.mkdir(projectDir, { recursive: true });
} else {
throw error;
}
} }
return projectDir; return projectDir;
} }
function deleteFolder(name){ async function deleteFolder(name) {
const projectDir = path.join(PROJECTS_DIR, `${name const projectDir = path.join(PROJECTS_DIR, `${name}`);
}`); try {
if (fs.existsSync await fs.access(projectDir);
(projectDir)) { await fs.rm(projectDir, { recursive: true, force: true });
fs.rmSync(projectDir, { recursive: true, force: true }); } catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
} }
} }
function scanAllImages(dir = 'storage') { async function scanAllImages(dir = 'storage') {
const projectDir = path.join(PROJECTS_DIR, dir); const projectDir = path.join(PROJECTS_DIR, dir);
let results = []; let results = [];
function scanDirectory(directory) { async function scanDirectory(directory) {
const files = fs.readdirSync(directory); const files = await fs.readdir(directory);
files.forEach(file => { for (const file of files) {
const filePath = path.join(directory, file); const filePath = path.join(directory, file);
const stat = fs.statSync(filePath); const stat = await fs.stat(filePath);
if (stat.isDirectory()) { if (stat.isDirectory()) {
scanDirectory(filePath); await scanDirectory(filePath);
} else if (file.endsWith('.jpg')) { } else if (file.endsWith('.jpg')) {
results.push(filePath); results.push(filePath);
} }
}); }
} }
scanDirectory(projectDir); await scanDirectory(projectDir);
return results; return results;
} }
function saveFile(filePath, content) { async function saveFile(filePath, content) {
// Ensure content is a buffer
if (Buffer.isBuffer(content)) { if (Buffer.isBuffer(content)) {
fs.writeFileSync(filePath, content); await fs.writeFile(filePath, content);
} else { } else {
throw new Error('Content must be a buffer'); throw new Error('Content must be a buffer');
} }
} }
function getFile(name){ async function getFile(name) {
const filePath = path.join(PROJECTS_DIR, `${name}`); const filePath = path.join(PROJECTS_DIR, `${name}`);
return fs return await fs.readFile(filePath);
.readFileSync(filePath);
} }
function deleteFile(name){ async function deleteFile(name) {
const filePath = path.join(PROJECTS_DIR, `${name}`); const filePath = path.join(PROJECTS_DIR, `${name}`);
if (fs.existsSync(filePath)) try {
fs.rmSync(filePath); await fs.access(filePath); // Vérifie si le fichier existe
await fs.rm(filePath); // Supprime le fichier
return `File ${filePath} deleted successfully.`;
} catch (error) {
if (error.code === 'ENOENT') {
return `File ${filePath} does not exist.`;
} else {
throw error; // Relance l'erreur si ce n'est pas une erreur de fichier introuvable
}
}
} }
module.exports = { module.exports = {

View File

@@ -7,13 +7,14 @@ async function uploadMeasureImage(image, projectId, orderId) {
const imagesDir = storageManager.createFolder(path.join(projectDir, 'images')); const imagesDir = storageManager.createFolder(path.join(projectDir, 'images'));
var imagePath = path.join(imagesDir, `${orderId}.jpg`); var imagePath = path.join(imagesDir, `${orderId}.jpg`);
storageManager.saveFile(imagePath, image.buffer); storageManager.saveFile(imagePath, image.buffer);
console.log("[FILE] uploadMeasureImage - Image saved to: " + imagePath);
return imagePath; return imagePath;
} }
async function getMeasureImage(projectId, orderId) { async function getMeasureImage(projectId, orderId) {
const projectPath = `${projectId}`; const projectPath = `${projectId}`;
const imagePath = `${projectPath}/${orderId}.jpg`; const imagePath = `${projectPath}/${orderId}.jpg`;
console.log("[FILE] getMeasureImage - Image path: " + imagePath);
return storageManager.getFile(imagePath); return storageManager.getFile(imagePath);
} }
@@ -21,6 +22,7 @@ async function getNextOrderId(projectId) {
const query = 'SELECT MAX(order_id) FROM public.measurements WHERE project_id = $1'; const query = 'SELECT MAX(order_id) FROM public.measurements WHERE project_id = $1';
const values = [projectId]; const values = [projectId];
const res = await db.query(query, values); const res = await db.query(query, values);
console.log("[DB] getNextOrderId - Max order_id: " + res.rows[0].max);
return res.rows[0].max + 1; return res.rows[0].max + 1;
} }
@@ -73,6 +75,13 @@ async function deleteMeasurement(id) {
return res.rows[0]; return res.rows[0];
} }
async function getPathFromIds(projectId, orderId) {
const query = 'SELECT path FROM public.measurements WHERE project_id = $1 AND order_id = $2';
const values = [projectId, orderId];
const res = await db.query(query, values);
return res.rows[0].path;
}
export { export {
uploadMeasureImage, uploadMeasureImage,
addMeasureToProject, addMeasureToProject,
@@ -83,5 +92,6 @@ export {
deleteMeasurement, deleteMeasurement,
getMeasureImage, getMeasureImage,
getMeasurementById, getMeasurementById,
updateMeasurementById updateMeasurementById,
getPathFromIds
} }

View File

@@ -6,17 +6,19 @@ function createProjectDirectory(projectId) {
storageManager.createFolder(projectPath); storageManager.createFolder(projectPath);
storageManager.createFolder(`${projectPath}/images`); storageManager.createFolder(`${projectPath}/images`);
storageManager.createFolder(`${projectPath}/videos`); storageManager.createFolder(`${projectPath}/videos`);
console.log("[FILE] createProjectDirectory : " + projectPath);
} }
function deleteProjectDirectory(projectId) { function deleteProjectDirectory(projectId) {
const projectPath = `${projectId}`; const projectPath = `${projectId}`;
storageManager.deleteFolder(projectPath); storageManager.deleteFolder(projectPath);
console.log("[FILE] deleteProjectDirectory : " + projectPath);
} }
async function getAllProjects() { async function getAllProjects() {
const query = 'SELECT * FROM public.projects'; const query = 'SELECT * FROM public.projects';
const res = await db.query(query); const res = await db.query(query);
console.log('getAllProjects:', res.rows); console.log("[DB] getAllProjects : ", res.rows);
return res.rows; return res.rows;
} }
@@ -24,6 +26,7 @@ async function getProjectById(projectId) {
const query = 'SELECT * FROM public.projects WHERE id = $1'; const query = 'SELECT * FROM public.projects WHERE id = $1';
const values = [projectId]; const values = [projectId];
const res = await db.query(query, values); const res = await db.query(query, values);
console.log("[DB] getProjectById : ", res.rows[0]);
return res.rows[0]; return res.rows[0];
} }
@@ -31,6 +34,7 @@ async function createProject(name, description, start_date, status) {
const query = 'INSERT INTO public.projects (name, description, start_date, status) VALUES ($1, $2, $3, $4) RETURNING *'; const query = 'INSERT INTO public.projects (name, description, start_date, status) VALUES ($1, $2, $3, $4) RETURNING *';
const values = [name, description, start_date, status]; const values = [name, description, start_date, status];
const res = await db.query(query, values); const res = await db.query(query, values);
console.log("[DB] createProject : ", res.rows[0]);
return res.rows[0]; return res.rows[0];
} }
@@ -38,12 +42,14 @@ async function editProjectById(projectID, name, description, startDate, status)
const query = 'UPDATE public.projects SET name = $1, description = $2, start_date = $3, status = $4 WHERE id = $5 RETURNING *'; const query = 'UPDATE public.projects SET name = $1, description = $2, start_date = $3, status = $4 WHERE id = $5 RETURNING *';
const values = [name, description, startDate, status, projectID]; const values = [name, description, startDate, status, projectID];
const res = await db.query(query, values); const res = await db.query(query, values);
console.log("[DB] editProjectById : ", res.rows[0]);
return res.rows[0]; return res.rows[0];
} }
async function deleteProjectById(projectId) { async function deleteProjectById(projectId) {
const query = 'DELETE FROM public.projects WHERE id = $1'; const query = 'DELETE FROM public.projects WHERE id = $1';
const values = [projectId]; const values = [projectId];
console.log("[DB] deleteProjectById : ", values);
await db.query(query, values); await db.query(query, values);
} }
@@ -51,6 +57,7 @@ async function getVideosByProjectId(projectId) {
const query = 'SELECT * FROM public.videos WHERE project_id = $1'; const query = 'SELECT * FROM public.videos WHERE project_id = $1';
const values = [projectId]; const values = [projectId];
const res = await db.query(query, values); const res = await db.query(query, values);
console.log("[DB] getVideosByProjectId : ", res.rows);
return res.rows; return res.rows;
} }
@@ -58,6 +65,7 @@ async function getMeasurementsByProjectId(projectId) {
const query = 'SELECT * FROM public.measurements WHERE project_id = $1'; const query = 'SELECT * FROM public.measurements WHERE project_id = $1';
const values = [projectId]; const values = [projectId];
const res = await db.query(query, values); const res = await db.query(query, values);
console.log("[DB] getMeasurementsByProjectId : ", res.rows);
return res.rows; return res.rows;
} }

111
src/video/videoManager.js Normal file
View File

@@ -0,0 +1,111 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const serverError = require('../../utils/serverError');
const db = require('../../db');
const storageManager = require('../data/storageManager');
const measureManager = require('../measure/measureManager');
const PROJECTS_DIR = path.join('.');
async function createVideoWithList(projectId, pathList) {
//pathList étant la liste des chemins déjà triés
const tempFile = path.join('temp.txt');
try {
// Trouver tous les fichiers image pour le projet donné
const workdir = path.join(PROJECTS_DIR, 'storage', `${projectId}`);
const dir = path.join(PROJECTS_DIR, 'storage', `${projectId}`, 'images');
console.log('dir:', dir);
const images = pathList;
console.log('images:', images);
// Trier les images numériquement
const sortedImages = images.sort((a, b) => {
const numA = parseInt(path.basename(a).match(/\d+/)[0], 10);
const numB = parseInt(path.basename(b).match(/\d+/)[0], 10);
return numA - numB;
});
// En déduire l'id de la première et dernière image utilisée
const firstImageId = parseInt(path.basename(sortedImages[0]).match(/\d+/)[0], 10);
const lastImageId = parseInt(path.basename(sortedImages[sortedImages.length - 1]).match(/\d+/)[0], 10);
console.log('firstImageId:', firstImageId);
console.log('lastImageId:', lastImageId);
// Créer un fichier temporaire pour la liste des images
fs.writeFileSync(tempFile, sortedImages.map(image => `file '${image}'`).join('\n'));
const frameRate = 10;
// le fichier final prend cette forme : {projectId}_{firstImageId}_{lastImageId}-{timestamp}.mp4
const timestamp = new Date().getTime();
const outputVideo = path.join(workdir, `${projectId}_${firstImageId}_${lastImageId}-${timestamp}.mp4`);
// Commande ffmpeg pour créer la vidéo
const ffmpegCommand = `ffmpeg -r ${frameRate} -f concat -safe 0 -i ${tempFile} -vsync vfr -pix_fmt yuv420p ${outputVideo}`;
console.log('Running ffmpeg command:', ffmpegCommand);
execSync(ffmpegCommand);
console.log('Video created successfully:', outputVideo);
return outputVideo;
} catch (error) {
console.error('Error creating video:', error);
serverError(error);
} finally {
// Supprimer le fichier temporaire
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
console.log('Temporary file deleted:', tempFile);
}
}
}
async function createVideo(projectId) {
const tempFile = path.join('temp.txt');
try {
// Trouver tous les fichiers image pour le projet donné
const workdir = path.join(PROJECTS_DIR, 'storage', `${projectId}`);
const dir = path.join(PROJECTS_DIR, 'storage', `${projectId}`, 'images');
console.log('dir:', dir);
const images = storageManager.scanAllImages(dir);
console.log('images:', images);
// Trier les images numériquement
const sortedImages = images.sort((a, b) => {
const numA = parseInt(path.basename(a).match(/\d+/)[0], 10);
const numB = parseInt(path.basename(b).match(/\d+/)[0], 10);
return numA - numB;
});
// En déduire l'id de la première et dernière image utilisée
const firstImageId = parseInt(path.basename(sortedImages[0]).match(/\d+/)[0], 10);
const lastImageId = parseInt(path.basename(sortedImages[sortedImages.length - 1]).match(/\d+/)[0], 10);
console.log('firstImageId:', firstImageId);
console.log('lastImageId:', lastImageId);
// Créer un fichier temporaire pour la liste des images
fs.writeFileSync(tempFile, sortedImages.map(image => `file '${image}'`).join('\n'));
const frameRate = 10;
const outputVideo = path.join(workdir, 'video.mp4');
// Commande ffmpeg pour créer la vidéo
const ffmpegCommand = `ffmpeg -r ${frameRate} -f concat -safe 0 -i ${tempFile} -vsync vfr -pix_fmt yuv420p ${outputVideo}`;
console.log('Running ffmpeg command:', ffmpegCommand);
execSync(ffmpegCommand);
console.log('Video created successfully:', outputVideo);
} catch (error) {
console.error('Error creating video:', error);
serverError(error);
} finally {
// Supprimer le fichier temporaire
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
console.log('Temporary file deleted:', tempFile);
}
}
}
module.exports = { createVideo, createVideoWithList };

View File

@@ -1,7 +1,9 @@
const storageManager = require('../src/data/storageManager'); const storageManager = require('../src/data/storageManager');
const videoManager = require('../src/video/videoManager');
const measureManager = require('../src/measure/measureManager');
const path = require('path'); const path = require('path');
console.log('Testing database functions...'); // console.log('Testing database functions...');
try { try {
storageManager.createFolder('test_folder'); storageManager.createFolder('test_folder');
@@ -16,4 +18,34 @@ function getSmileImage() {
return path.join(__dirname, '../sample/smile.png'); return path.join(__dirname, '../sample/smile.png');
} }
//test de lancement d'une création de vidéo sur le projet 1
// videoManager.createVideo(1).then(res => {
// console.log('3 - Video created:', res);
// }).catch(err => {
// console.error('Error creating video:', err);
// });
// async function run() {
// var Path = await measureManager.getPathFromIds(1, 1);
// console.log(Path);
// }
// run().catch(err => {
// console.error('Error:', err);
// });
var pathList = [
'storage/1/images/1.jpg',
'storage/1/images/10.jpg',
'storage/1/images/20.jpg',
'storage/1/images/30.jpg',
];
videoManager.createVideoWithList(1, pathList).then(res => {
console.log('3 - Video created:', res);
return storageManager.deleteFile(res);
}).then(res => {
console.log('4 - Video deleted:', res);
}).catch(err => {
console.error('Error:', err);
});
exports.getSmileImage = getSmileImage; exports.getSmileImage = getSmileImage;

View File

@@ -1,31 +0,0 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const serverError = require('../utils/serverError');
async function createVideo(projectId) {
const imageDir = `/storage/${projectId}`;
const outputVideo = `/storage/videos/output_${projectId}_video.mp4`;
const frameRate = 24;
const tempFile = `/storage/${projectId}/temp_file.txt`;
try {
const images = fs.readdirSync(imageDir).filter(file => file.endsWith('.jpg'));
if (images.length === 0) {
throw new Error('No images found for this project');
}
const tempFileContent = images.map(img => `file '${path.join(imageDir, img)}'`).join('\n');
fs.writeFileSync(tempFile, tempFileContent);
const ffmpegCommand = `ffmpeg -r ${frameRate} -f concat -safe 0 -i ${tempFile} -vsync vfr -pix_fmt yuv420p ${outputVideo}`;
execSync(ffmpegCommand);
fs.unlinkSync(tempFile);
return { message: 'Video created successfully', videoPath: outputVideo };
} catch (error) {
throw new Error(`Error creating video: ${error.message}`);
}
}
module.exports = { createVideo };