Refactor la création de vidéos pour utiliser des promesses et améliorer la gestion des erreurs, avec une réponse immédiate au démarrage du rendu.

This commit is contained in:
2025-03-13 11:28:17 +01:00
parent 2e552be9db
commit c90ff42961
2 changed files with 102 additions and 79 deletions

View File

@@ -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);
}
}
}