Ajouter une route pour récupérer la progression de la création de vidéos et améliorer la gestion des erreurs dans la fonction createVideoWithList

This commit is contained in:
2025-03-13 11:50:31 +01:00
parent a069acfce7
commit c3e78b248f
2 changed files with 155 additions and 40 deletions

View File

@@ -202,6 +202,46 @@ router.get('/videos/reset/:video_id', (req, res) => {
});
});
router.get('/videos/progress/:video_id', async (req, res) => {
try {
const result = await db.query(`
SELECT
progress,
EXTRACT(EPOCH FROM (NOW() - started_at)) as elapsed,
eta,
status
FROM public.videos
WHERE id = $1
`, [req.params.video_id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Vidéo non trouvée' });
}
const video = result.rows[0];
res.json({
progress: video.progress,
elapsed: video.elapsed,
eta: video.eta,
status: this.getStatusLabel(video.status)
});
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Erreur de récupération' });
}
});
function getStatusLabel(status) {
const statusMap = {
0: 'En attente',
1: 'Terminé',
2: 'Échec',
3: 'En cours'
};
return statusMap[status] || 'Inconnu';
}
router.get('/cat', (_, res) => {
const videoPath = dbTester.getCatVideo();

View File

@@ -4,6 +4,8 @@ const { execSync } = require('child_process');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const { spawn } = require('child_process');
let globalProgress = {};
const serverError = require('../../utils/serverError');
const db = require('../../db');
@@ -32,18 +34,17 @@ async function deleteVideoProject(videoId) {
async function createVideoWithList(projectId, pathList, duration, videoId) {
const tempFile = path.join('temp.txt');
let ffmpegSuccess = false;
let ffmpegProcess;
let cleanupDone = false;
try {
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
// Configuration des chemins
const workdir = path.join(PROJECTS_DIR, 'storage', projectId.toString());
if (!fs.existsSync(workdir)) {
fs.mkdirSync(workdir, { recursive: true });
}
// Triage des images
// 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);
@@ -53,65 +54,139 @@ async function createVideoWithList(projectId, pathList, duration, videoId) {
// Création du fichier temporaire
fs.writeFileSync(tempFile, sortedImages.map(image => `file '${image}'`).join('\n'));
// Calcul du frame rate
const frameRate = Math.ceil(sortedImages.length / parseInt(duration));
// Génération du nom de fichier
// 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}_${path.basename(sortedImages[0], path.extname(sortedImages[0]))}_${path.basename(sortedImages[sortedImages.length - 1], path.extname(sortedImages[sortedImages.length - 1]))}-${timestamp}.mp4`
`${projectId}_${firstImageId}_${lastImageId}-${timestamp}.mp4`
);
// Commande FFmpeg
const ffmpegCommand = [
'ffmpeg',
'-y', // Overwrite output file
'-r', frameRate,
// Mise à jour initiale de la base de données
await db.query(`
UPDATE public.videos
SET
status = 3,
progress = 0,
started_at = NOW(),
updated_at = NOW(),
eta = NULL
WHERE id = $1
`, [videoId]);
// Configuration de FFmpeg
const ffmpegArgs = [
'-y',
'-r', frameRate.toString(),
'-f', 'concat',
'-safe', '0',
'-i', tempFile,
'-vsync', 'vfr',
'-pix_fmt', 'yuv420p',
outputVideo
].join(' ');
];
console.log(`Exécution de la commande FFmpeg: ${ffmpegCommand}`);
const { stderr } = await execPromise(ffmpegCommand);
ffmpegProcess = spawn('ffmpeg', ffmpegArgs, {
stdio: ['ignore', 'ignore', 'pipe']
});
// Vérification des erreurs FFmpeg
if (stderr.includes('Error') || stderr.includes('failed')) {
throw new Error(`Erreur FFmpeg: ${stderr}`);
}
let lastUpdate = 0;
const startTime = Date.now();
ffmpegSuccess = true;
// É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 de la base de données
const updateStatusRes = await db.query(
'UPDATE public.videos SET status = $1, video_file = $2 WHERE id = $3 RETURNING *',
[1, outputVideo, videoId]
);
// Mise à jour max toutes les 500ms
if (now - lastUpdate > 500) {
db.query(`
UPDATE public.videos
SET
progress = $1,
eta = $2,
updated_at = NOW()
WHERE id = $3
`, [progress, Math.round(eta), videoId]).catch(console.error);
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]);
resolve();
} catch (e) {
reject(e);
}
} else {
reject(new Error(`FFmpeg process exited with code ${code}`));
}
});
ffmpegProcess.on('error', reject);
});
console.log('Vidéo et statut mis à jour:', updateStatusRes.rows[0]);
return outputVideo;
} catch (error) {
console.error('Erreur lors de la création vidéo:', error);
// Gestion des erreurs
console.error('Error in video creation:', 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
);
try {
await db.query(`
UPDATE public.videos
SET
status = 2,
progress = 0,
eta = 0,
updated_at = NOW()
WHERE id = $1
`, [videoId]);
} catch (dbError) {
console.error('Database update error:', dbError);
}
throw error;
} finally {
// Nettoyage du fichier temporaire
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
// Nettoyage
if (!cleanupDone) {
if (tempFile && fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
if (ffmpegProcess) {
ffmpegProcess.kill();
}
cleanupDone = true;
}
}
}