const sharp = require('sharp'); const path = require('path'); const fs = require('fs').promises; // Configure sharp for better memory management with large batches sharp.cache(false); // Disable cache to prevent memory buildup sharp.concurrency(2); // Limit concurrent operations const THUMBNAIL_WIDTH = 300; const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '../../../storage'); const getThumbnailPath = () => path.join(getStoragePath(), 'thumbnails'); async function generateThumbnail(imagePath) { const filename = path.basename(imagePath); const thumbnailFilename = `thumb_${filename}`; const thumbnailDir = getThumbnailPath(); const thumbnailPath = path.join(thumbnailDir, thumbnailFilename); // Ensure thumbnail directory exists await fs.mkdir(thumbnailDir, { recursive: true }); try { // Generate thumbnail with memory-efficient settings await sharp(imagePath, { limitInputPixels: 268402689, // ~16k x 16k max sequentialRead: true // More memory efficient for large images }) .resize(THUMBNAIL_WIDTH, null, { withoutEnlargement: true, fit: 'inside' }) .jpeg({ quality: 80, progressive: true, // Progressive JPEG for better loading mozjpeg: true // Better compression }) .toFile(thumbnailPath); return path.relative(getStoragePath(), thumbnailPath); } catch (error) { console.error(`Failed to generate thumbnail for ${filename}:`, error); // Return null if thumbnail generation fails, don't fail the whole upload return null; } } module.exports = { generateThumbnail };