const fs = require('fs'); const path = require('path'); const { spawn } = require('child_process'); const database_manager = require('../database/database_manager'); const PROJECTS_DIR = path.join('.'); async function createVideoWithList(projectId, pathList, duration, videoId, res_width, res_height) { const tempFile = path.join('temp.txt'); let ffmpegProcess; let cleanupDone = false; try { // Configuration des chemins const workdir = path.join(PROJECTS_DIR, 'storage', projectId.toString()); if (!fs.existsSync(workdir)) { fs.mkdirSync(workdir, { recursive: true }); } // Tri 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; }); // Création du fichier temporaire fs.writeFileSync(tempFile, sortedImages.map(image => `file '${image}'`).join('\n')); // Calcul des paramètres vidéo const totalFrames = sortedImages.length; const frameRate = Math.ceil(totalFrames / parseInt(duration)); const timestamp = Date.now(); const firstImageId = path.basename(sortedImages[0]).match(/\d+/)[0]; const lastImageId = path.basename(sortedImages[sortedImages.length - 1]).match(/\d+/)[0]; const outputVideo = path.join( workdir, `${projectId}_${firstImageId}_${lastImageId}-${timestamp}.mp4` ); // Mise à jour initiale de la base de données let edit_video = { status: 3, progress: 0, started_at: new Date(), updated_at: new Date(), eta: null } await database_manager.video.edit_video_by_id(videoId, edit_video) const scale = res_width && res_height ? `scale=${res_width}:${res_height}` : 'scale=854:480'; // Redimensionne la vidéo en 480p par défaut // Configuration de FFmpeg const ffmpegArgs = [ '-y', '-r', frameRate.toString(), '-f', 'concat', '-safe', '0', '-i', tempFile, '-vsync', 'vfr', '-pix_fmt', 'yuv420p', '-vf', scale, '-b:v', '1500k', // Force un bitrate vidéo de 1500 kbps (ajuste si nécessaire) outputVideo ]; ffmpegProcess = spawn('ffmpeg', ffmpegArgs, { stdio: ['ignore', 'ignore', 'pipe'] }); let lastUpdate = 0; const startTime = Date.now(); // Écoute de la sortie d'erreur pour la progression ffmpegProcess.stderr.on('data', (data) => { const output = data.toString(); const frameMatch = output.match(/frame=\s*(\d+)/); if (frameMatch) { const currentFrame = parseInt(frameMatch[1], 10); const progress = Math.min((currentFrame / totalFrames) * 100, 99.99); const now = Date.now(); // Calcul de l'ETA const elapsedSeconds = (now - startTime) / 1000; const eta = elapsedSeconds / (currentFrame / totalFrames) - elapsedSeconds; // Mise à jour max toutes les 500ms if (now - lastUpdate > 500) { let update_video = { progress: progress, eta: Math.round(eta), updated_at: new Date() } database_manager.video.edit_video_by_id(videoId, update_video) console.log('Progress:', progress.toFixed(2), '%, ETA:', eta.toFixed(0), 's'); lastUpdate = now; } } }); // Attente de la fin du processus await new Promise((resolve, reject) => { ffmpegProcess.on('close', async (code) => { if (code === 0) { try { // Mise à jour finale // await db.query(` // UPDATE public.videos // SET // status = 1, // progress = 100, // eta = 0, // video_file = $1, // updated_at = NOW() // WHERE id = $2 // `, [outputVideo, videoId]); let latest_update = { status: 1, progress: 100, eta: 0, video_file: outputVideo, updated_at: new Date() } await database_manager.video.edit_video_by_id(videoId, latest_update) resolve(); } catch (e) { reject(e); } } else { reject(new Error(`FFmpeg process exited with code ${code}`)); } }); ffmpegProcess.on('error', reject); }); return outputVideo; } catch (error) { // Gestion des erreurs console.error('Error in video creation:', error); try { // Mise à jour de la base de données en cas d'erreur let error_video = { status: 0, progress: 0, eta: null, updated_at: new Date() } await database_manager.video.edit_video_by_id(videoId, error_video) } catch (dbError) { console.error('Database update error:', dbError); } throw error; } finally { // Nettoyage if (!cleanupDone) { if (tempFile && fs.existsSync(tempFile)) { fs.unlinkSync(tempFile); } if (ffmpegProcess) { ffmpegProcess.kill(); } cleanupDone = true; } } } module.exports = { createVideoWithList };