From c90ff42961de716ab010c88a1c2333b33dad5c5c Mon Sep 17 00:00:00 2001 From: dakerboul Date: Thu, 13 Mar 2025 11:28:17 +0100 Subject: [PATCH] =?UTF-8?q?Refactor=20la=20cr=C3=A9ation=20de=20vid=C3=A9o?= =?UTF-8?q?s=20pour=20utiliser=20des=20promesses=20et=20am=C3=A9liorer=20l?= =?UTF-8?q?a=20gestion=20des=20erreurs,=20avec=20une=20r=C3=A9ponse=20imm?= =?UTF-8?q?=C3=A9diate=20au=20d=C3=A9marrage=20du=20rendu.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/videoRoutes.js | 62 ++++++++++---------- src/video/videoManager.js | 119 +++++++++++++++++++++++--------------- 2 files changed, 102 insertions(+), 79 deletions(-) diff --git a/routes/videoRoutes.js b/routes/videoRoutes.js index d536282..a8e2bf3 100644 --- a/routes/videoRoutes.js +++ b/routes/videoRoutes.js @@ -135,43 +135,41 @@ function serveFallbackVideo(res) { router.post('/videos/render/:video_id', async (req, res) => { - const videoId = req.params.video_id; - const query = 'SELECT measurement_ids, project_id, duration FROM public.videos WHERE id = $1'; + try { + const videoId = req.params.video_id; + const result = await db.query( + 'SELECT measurement_ids, project_id, duration FROM public.videos WHERE id = $1', + [videoId] + ); - db.query(query, [videoId], async (err, results) => { - if (err) { - return serverError.sendError('Error getting video:', res, err, 500); - } - if (results.rows.length === 0) { - return res.status(404).json({ error: 'Video not found' }); - } + if (result.rows.length === 0) { + return res.status(404).json({ error: 'Vidéo non trouvée' }); + } - console.log('Video found:', results.rows[0]); - const duration = results.rows[0].duration; + const { duration, measurement_ids, project_id } = result.rows[0]; + const pathList = await measureManager.getPathList(measurement_ids, project_id); - console.log('Rendering video:', videoId); + // Démarrage du traitement en arrière-plan + videoManager.createVideoWithList(project_id, pathList, duration, videoId) + .then(videoFile => { + console.log('Rendu vidéo terminé:', videoFile); + return videoManager.updateVideoFile(videoId, videoFile); + }) + .catch(error => { + console.error('Échec du rendu vidéo:', error); + }); - const measurementIds = results.rows[0].measurement_ids; - const project_id = results.rows[0].project_id; - console.log('Measurement IDs:', measurementIds); - console.log('Project ID:', project_id); + // Réponse immédiate + res.json({ + status: 'processing', + message: 'Le rendu a démarré', + check_url: `/videos/status/${videoId}` + }); - try { - const pathList = await measureManager.getPathList(measurementIds, project_id); - console.log('Path list:', pathList); - res.json({ message: 'Render process started' }); - - const videoFile = await videoManager.createVideoWithList(project_id, pathList, duration, videoId); - console.log('Video file:', videoFile); - - const update = await videoManager.updateVideoFile(videoId, videoFile); - - console.log('Video rendering complete'); - } catch (err) { - console.error('Error during video rendering:', err); - res.status(500).json({ error: 'Error during video rendering' }); - } - }); + } catch (error) { + console.error('Erreur initiale:', error); + res.status(500).json({ error: 'Échec de l\'initialisation du rendu' }); + } }); router.get('/videos/reset/:video_id', (req, res) => { diff --git a/src/video/videoManager.js b/src/video/videoManager.js index e1f128e..0634f85 100644 --- a/src/video/videoManager.js +++ b/src/video/videoManager.js @@ -1,6 +1,9 @@ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); +const { exec } = require('child_process'); +const util = require('util'); +const execPromise = util.promisify(exec); const serverError = require('../../utils/serverError'); const db = require('../../db'); @@ -28,66 +31,88 @@ async function deleteVideoProject(videoId) { } async function createVideoWithList(projectId, pathList, duration, videoId) { - //pathList étant la liste des chemins déjà triés const tempFile = path.join('temp.txt'); + let ffmpegSuccess = false; + 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); + const workdir = path.join(PROJECTS_DIR, 'storage', `${projectId}`); + const dir = path.join(PROJECTS_DIR, 'storage', `${projectId}`, 'images'); + + // Vérification de l'existence du répertoire + if (!fs.existsSync(workdir)) { + fs.mkdirSync(workdir, { recursive: true }); + } - // 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; - }); + // Triage des images + const sortedImages = pathList.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); + // Création du fichier temporaire + fs.writeFileSync(tempFile, sortedImages.map(image => `file '${image}'`).join('\n')); - console.log('firstImageId:', firstImageId); - console.log('lastImageId:', lastImageId); + // Calcul du frame rate + const frameRate = Math.ceil(sortedImages.length / parseInt(duration)); - // Créer un fichier temporaire pour la liste des images - fs.writeFileSync(tempFile, sortedImages.map(image => `file '${image}'`).join('\n')); + // Génération du nom de fichier + const timestamp = Date.now(); + const outputVideo = path.join( + workdir, + `${projectId}_${path.basename(sortedImages[0], path.extname(sortedImages[0]))}_${path.basename(sortedImages[sortedImages.length - 1], path.extname(sortedImages[sortedImages.length - 1]))}-${timestamp}.mp4` + ); - const frameRate = Math.ceil(sortedImages.length / parseInt(duration)); + // Commande FFmpeg + const ffmpegCommand = [ + 'ffmpeg', + '-y', // Overwrite output file + '-r', frameRate, + '-f', 'concat', + '-safe', '0', + '-i', tempFile, + '-vsync', 'vfr', + '-pix_fmt', 'yuv420p', + outputVideo + ].join(' '); - // 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`); + console.log(`Exécution de la commande FFmpeg: ${ffmpegCommand}`); + const { stderr } = await execPromise(ffmpegCommand); - // 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).then(() => { - console.log('Video created successfully:', outputVideo); + // Vérification des erreurs FFmpeg + if (stderr.includes('Error') || stderr.includes('failed')) { + throw new Error(`Erreur FFmpeg: ${stderr}`); + } - // Mettre à jour le statut de la vidéo à "completed" - const updateStatusQuery = 'UPDATE public.videos SET status = $2 WHERE id = $1 RETURNING *'; - const updateStatusValues = [videoId, 1]; // 1 pour le statut "completed" - const updateStatusRes = db.query(updateStatusQuery, updateStatusValues); - console.log('Video status updated to completed:', updateStatusRes.rows[0]); - } - ).catch((error) => { - console.error('Error creating video:', error); - serverError.sendError('Error creating video', err=error); - }); + ffmpegSuccess = true; + + // Mise à jour de la base de données + const updateStatusRes = await db.query( + 'UPDATE public.videos SET status = $1, file_path = $2 WHERE id = $3 RETURNING *', + [1, outputVideo, videoId] + ); + + console.log('Vidéo et statut mis à jour:', updateStatusRes.rows[0]); + return outputVideo; - return outputVideo; } catch (error) { - console.error('Error creating video:', error); - serverError.sendError('Error creating video', err=error); + console.error('Erreur lors de la création vidéo:', error); + + // Mise à jour du statut en erreur + if (ffmpegSuccess) { + await db.query( + 'UPDATE public.videos SET status = $1 WHERE id = $2', + [3, videoId] // 3 = statut erreur + ); + } + + throw error; + } finally { - // Supprimer le fichier temporaire - if (fs.existsSync(tempFile)) { - fs.unlinkSync(tempFile); - console.log('Temporary file deleted:', tempFile); - } + // Nettoyage du fichier temporaire + if (fs.existsSync(tempFile)) { + fs.unlinkSync(tempFile); + } } }