// api_v1/scanner/thumbs.js const sharp = require('sharp'); const { exec } = require('child_process'); const path = require('path'); function deriveCastPathFromAvg(thumbAvgPath) { if (!thumbAvgPath) return null; // Caso standard: IMG_0241_avg.jpg -> IMG_0241_cast.jpg if (thumbAvgPath.toLowerCase().endsWith('_avg.jpg')) { return thumbAvgPath.replace(/_avg\.jpg$/i, '_cast.jpg'); } // Fallback generico const ext = path.extname(thumbAvgPath); const base = thumbAvgPath.slice(0, -ext.length); return `${base}_cast.jpg`; } // Video: lascia solo avg + min function createVideoThumbnail(videoPath, thumbMinPath, thumbAvgPath) { return new Promise((resolve) => { const cmd = ` ffmpeg -y -i "${videoPath}" -ss 00:00:01.000 -vframes 1 "${thumbAvgPath}" && ffmpeg -y -i "${thumbAvgPath}" -vf "scale=100:-1" "${thumbMinPath}" `; exec(cmd, () => resolve()); }); } // Foto: min + avg + cast async function createThumbnails(filePath, thumbMinPath, thumbAvgPath, thumbCastPath) { try { const finalCastPath = thumbCastPath || deriveCastPathFromAvg(thumbAvgPath); // 1) MIN: 100px await sharp(filePath) .rotate() .resize({ width: 100, height: 100, fit: 'inside', withoutEnlargement: true, }) .jpeg({ quality: 75, progressive: false, optimizeCoding: true, }) .toFile(thumbMinPath); // 2) AVG: 400px await sharp(filePath) .rotate() .resize({ width: 400, fit: 'inside', withoutEnlargement: true, }) .jpeg({ quality: 78, progressive: false, optimizeCoding: true, }) .toFile(thumbAvgPath); // 3) CAST: max 4K TV, dentro 3840x2160, senza crop // 3) CAST: metà del 4K → 1920x1080, senza crop if (finalCastPath) { await sharp(filePath) .rotate() .resize({ width: 1920, height: 1080, fit: 'inside', withoutEnlargement: true, fastShrinkOnLoad: true, }) .jpeg({ quality: 85, progressive: false, optimizeCoding: true, }) .toFile(finalCastPath); } } catch (err) { console.error('Errore creazione thumbnails:', err.message, filePath); } } module.exports = { createVideoThumbnail, createThumbnails, };