diff --git a/db.js b/db.js index 04bfb08..8f3ba17 100644 --- a/db.js +++ b/db.js @@ -1,6 +1,6 @@ const { Client } = require('pg'); -const local = false; +const local = true; // Connexion à la base de données PostgreSQL const client = new Client({ host: local ? 'mikoshi' : '172.30.0.2', @@ -16,7 +16,7 @@ function connectWithRetry() { console.error('Erreur de connexion à la base de données:', err); setTimeout(connectWithRetry, 30000); // Réessayer après 30 secondes } else { - console.log('Connecté à la base de données PostgreSQL.'); + console.log('[DB] Connecté à la base de données PostgreSQL.'); } }); } diff --git a/routes/videoRoutes.js b/routes/videoRoutes.js index ceaa597..406dbc6 100644 --- a/routes/videoRoutes.js +++ b/routes/videoRoutes.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const db = require('../db'); const serverError = require('../utils/serverError'); +const videoManager = require('../src/video/videoManager'); /** * @swagger diff --git a/server.js b/server.js index 3778a51..5987951 100644 --- a/server.js +++ b/server.js @@ -60,6 +60,6 @@ app.get('/', (req, res) => { // Démarrer le serveur app.listen(port, () => { - console.log(`Serveur démarré sur http://localhost:${port}`); - console.log(`Swagger documentation disponible sur http://localhost:${port}/api-docs`); + console.log(`[SERVER] Serveur démarré sur http://localhost:${port}`); + console.log(`[SERVER] Swagger documentation disponible sur http://localhost:${port}/api-docs`); }); diff --git a/src/data/filewatcher.js b/src/data/filewatcher.js index 9fa18fb..d1907d3 100644 --- a/src/data/filewatcher.js +++ b/src/data/filewatcher.js @@ -3,17 +3,20 @@ import path from 'path'; import storageManager from '../data/storageManager.js'; import fs from 'fs'; +let localCounter = 0; + async function checkAndRemoveInvalidEntries() { - console.log('Checking for invalid entries...'); + localCounter = 0; + console.log('[INFO] Vérification et suppression des entrées invalides...'); try { const measurementsRes = await db.query('SELECT id, path FROM measurements'); //console.log('Fetched measurements:', measurementsRes.rows); for (const row of measurementsRes.rows) { //console.log('Checking file path:', row.path); if (!fs.existsSync(row.path)) { - // Remove invalid entry await db.query('DELETE FROM measurements WHERE id = $1', [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 fs.unlinkSync(imagePath); 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) { console.error('Error checking and removing invalid entries:', err); } @@ -36,7 +45,8 @@ async function checkAndRemoveInvalidEntries() { // 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 checkAndRemoveInvalidEntries(); \ No newline at end of file diff --git a/src/data/storageManager.js b/src/data/storageManager.js index 5fe4f0f..a2e2245 100644 --- a/src/data/storageManager.js +++ b/src/data/storageManager.js @@ -1,64 +1,80 @@ -const fs = require('fs'); +const fs = require('fs').promises; const path = require('path'); const PROJECTS_DIR = path.join('.'); -function createFolder(name){ +async function createFolder(name) { const projectDir = path.join(PROJECTS_DIR, `${name}`); - if (!fs.existsSync(projectDir)) { - fs.mkdirSync(projectDir, { recursive: true }); + try { + await fs.access(projectDir); + } catch (error) { + if (error.code === 'ENOENT') { + await fs.mkdir(projectDir, { recursive: true }); + } else { + throw error; + } } return projectDir; } -function deleteFolder(name){ - const projectDir = path.join(PROJECTS_DIR, `${name - }`); - if (fs.existsSync - (projectDir)) { - fs.rmSync(projectDir, { recursive: true, force: true }); +async function deleteFolder(name) { + const projectDir = path.join(PROJECTS_DIR, `${name}`); + try { + await fs.access(projectDir); + await fs.rm(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); let results = []; - - function scanDirectory(directory) { - const files = fs.readdirSync(directory); - files.forEach(file => { + + async function scanDirectory(directory) { + const files = await fs.readdir(directory); + for (const file of files) { const filePath = path.join(directory, file); - const stat = fs.statSync(filePath); + const stat = await fs.stat(filePath); if (stat.isDirectory()) { - scanDirectory(filePath); + await scanDirectory(filePath); } else if (file.endsWith('.jpg')) { results.push(filePath); } - }); + } } - scanDirectory(projectDir); + await scanDirectory(projectDir); return results; } -function saveFile(filePath, content) { - // Ensure content is a buffer +async function saveFile(filePath, content) { if (Buffer.isBuffer(content)) { - fs.writeFileSync(filePath, content); + await fs.writeFile(filePath, content); } else { throw new Error('Content must be a buffer'); } } -function getFile(name){ +async function getFile(name) { const filePath = path.join(PROJECTS_DIR, `${name}`); - return fs - .readFileSync(filePath); + return await fs.readFile(filePath); } -function deleteFile(name){ +async function deleteFile(name) { const filePath = path.join(PROJECTS_DIR, `${name}`); - if (fs.existsSync(filePath)) - fs.rmSync(filePath); + try { + 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 = { diff --git a/src/measure/measureManager.js b/src/measure/measureManager.js index 9634e17..ed200f9 100644 --- a/src/measure/measureManager.js +++ b/src/measure/measureManager.js @@ -7,13 +7,14 @@ async function uploadMeasureImage(image, projectId, orderId) { const imagesDir = storageManager.createFolder(path.join(projectDir, 'images')); var imagePath = path.join(imagesDir, `${orderId}.jpg`); storageManager.saveFile(imagePath, image.buffer); - + console.log("[FILE] uploadMeasureImage - Image saved to: " + imagePath); return imagePath; } async function getMeasureImage(projectId, orderId) { const projectPath = `${projectId}`; const imagePath = `${projectPath}/${orderId}.jpg`; + console.log("[FILE] getMeasureImage - Image path: " + 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 values = [projectId]; const res = await db.query(query, values); + console.log("[DB] getNextOrderId - Max order_id: " + res.rows[0].max); return res.rows[0].max + 1; } @@ -73,6 +75,13 @@ async function deleteMeasurement(id) { 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 { uploadMeasureImage, addMeasureToProject, @@ -83,5 +92,6 @@ export { deleteMeasurement, getMeasureImage, getMeasurementById, - updateMeasurementById + updateMeasurementById, + getPathFromIds } diff --git a/src/project/projectManager.js b/src/project/projectManager.js index ed5e0ca..5d71d70 100644 --- a/src/project/projectManager.js +++ b/src/project/projectManager.js @@ -6,17 +6,19 @@ function createProjectDirectory(projectId) { storageManager.createFolder(projectPath); storageManager.createFolder(`${projectPath}/images`); storageManager.createFolder(`${projectPath}/videos`); + console.log("[FILE] createProjectDirectory : " + projectPath); } function deleteProjectDirectory(projectId) { const projectPath = `${projectId}`; storageManager.deleteFolder(projectPath); + console.log("[FILE] deleteProjectDirectory : " + projectPath); } async function getAllProjects() { const query = 'SELECT * FROM public.projects'; const res = await db.query(query); - console.log('getAllProjects:', res.rows); + console.log("[DB] getAllProjects : ", res.rows); return res.rows; } @@ -24,6 +26,7 @@ async function getProjectById(projectId) { const query = 'SELECT * FROM public.projects WHERE id = $1'; const values = [projectId]; const res = await db.query(query, values); + console.log("[DB] getProjectById : ", 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 values = [name, description, start_date, status]; const res = await db.query(query, values); + console.log("[DB] createProject : ", 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 values = [name, description, startDate, status, projectID]; const res = await db.query(query, values); + console.log("[DB] editProjectById : ", res.rows[0]); return res.rows[0]; } async function deleteProjectById(projectId) { const query = 'DELETE FROM public.projects WHERE id = $1'; const values = [projectId]; + console.log("[DB] deleteProjectById : ", 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 values = [projectId]; const res = await db.query(query, values); + console.log("[DB] getVideosByProjectId : ", res.rows); return res.rows; } @@ -58,6 +65,7 @@ async function getMeasurementsByProjectId(projectId) { const query = 'SELECT * FROM public.measurements WHERE project_id = $1'; const values = [projectId]; const res = await db.query(query, values); + console.log("[DB] getMeasurementsByProjectId : ", res.rows); return res.rows; } diff --git a/src/video/videoManager.js b/src/video/videoManager.js new file mode 100644 index 0000000..8a6e7a6 --- /dev/null +++ b/src/video/videoManager.js @@ -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 }; diff --git a/test/tester.js b/test/tester.js index 87f286c..3b7c681 100644 --- a/test/tester.js +++ b/test/tester.js @@ -1,7 +1,9 @@ const storageManager = require('../src/data/storageManager'); +const videoManager = require('../src/video/videoManager'); +const measureManager = require('../src/measure/measureManager'); const path = require('path'); -console.log('Testing database functions...'); +// console.log('Testing database functions...'); try { storageManager.createFolder('test_folder'); @@ -16,4 +18,34 @@ function getSmileImage() { 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; \ No newline at end of file diff --git a/utils/video.js b/utils/video.js deleted file mode 100644 index 168aaea..0000000 --- a/utils/video.js +++ /dev/null @@ -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 }; \ No newline at end of file