UNPKG

insta-meme-bot-cli

Version:

Production-ready Instagram meme bot that scrapes memes from Pinterest and posts them automatically with CLI and programmatic API support

187 lines (166 loc) 5.57 kB
const sharp = require('sharp'); const fs = require('fs'); const path = require('path'); /** * Image processing utilities for Instagram compatibility */ class ImageProcessor { /** * Resize and optimize image for Instagram posting * @param {string} inputPath - Path to input image * @param {string} outputPath - Path for output image (optional) * @returns {Promise<string>} Path to processed image */ static async processForInstagram(inputPath, outputPath = null) { if (!fs.existsSync(inputPath)) { throw new Error(`Input image not found: ${inputPath}`); } // Generate output path if not provided if (!outputPath) { const ext = path.extname(inputPath); const basename = path.basename(inputPath, ext); const dirname = path.dirname(inputPath); outputPath = path.join(dirname, `${basename}_instagram${ext}`); } try { // Get image metadata const metadata = await sharp(inputPath).metadata(); // Instagram requirements: // - Max resolution: 1080x1080 for square, 1080x1350 for portrait, 1080x566 for landscape // - Supported formats: JPEG, PNG // - Max file size: 8MB (we'll aim for much smaller) let width, height; const { width: originalWidth, height: originalHeight } = metadata; const aspectRatio = originalWidth / originalHeight; // Determine optimal dimensions based on aspect ratio if (aspectRatio === 1) { // Square image width = Math.min(1080, originalWidth); height = width; } else if (aspectRatio < 1) { // Portrait image (taller than wide) height = Math.min(1350, originalHeight); width = Math.round(height * aspectRatio); if (width > 1080) { width = 1080; height = Math.round(width / aspectRatio); } } else { // Landscape image (wider than tall) width = Math.min(1080, originalWidth); height = Math.round(width / aspectRatio); if (height < 566) { height = 566; width = Math.round(height * aspectRatio); } } // Process the image await sharp(inputPath) .resize(width, height, { fit: 'inside', withoutEnlargement: true }) .jpeg({ quality: 85, progressive: true }) .toFile(outputPath); // Verify file size (Instagram max is 8MB, we aim for under 2MB) const stats = fs.statSync(outputPath); const fileSizeMB = stats.size / (1024 * 1024); if (fileSizeMB > 2) { // Re-process with lower quality if too large await sharp(inputPath) .resize(width, height, { fit: 'inside', withoutEnlargement: true }) .jpeg({ quality: 70, progressive: true }) .toFile(outputPath); } return outputPath; } catch (error) { throw new Error(`Image processing failed: ${error.message}`); } } /** * Validate if image meets Instagram requirements * @param {string} imagePath - Path to image file * @returns {Promise<Object>} Validation result */ static async validateForInstagram(imagePath) { try { const metadata = await sharp(imagePath).metadata(); const stats = fs.statSync(imagePath); const fileSizeMB = stats.size / (1024 * 1024); const issues = []; const { width, height, format } = metadata; const aspectRatio = width / height; // Check format if (!['jpeg', 'jpg', 'png'].includes(format.toLowerCase())) { issues.push(`Unsupported format: ${format}. Use JPEG or PNG.`); } // Check file size if (fileSizeMB > 8) { issues.push(`File too large: ${fileSizeMB.toFixed(2)}MB. Max is 8MB.`); } // Check dimensions if (width > 1080 || height > 1350) { issues.push(`Resolution too high: ${width}x${height}. Max is 1080x1350.`); } // Check aspect ratio if (aspectRatio < 0.8 || aspectRatio > 1.91) { issues.push(`Aspect ratio ${aspectRatio.toFixed(2)} not supported. Range: 0.8-1.91.`); } return { valid: issues.length === 0, issues, metadata: { width, height, format, fileSize: fileSizeMB, aspectRatio } }; } catch (error) { return { valid: false, issues: [`Image validation failed: ${error.message}`], metadata: null }; } } /** * Get optimal dimensions for Instagram based on aspect ratio * @param {number} width - Original width * @param {number} height - Original height * @returns {Object} Optimal dimensions */ static getOptimalDimensions(width, height) { const aspectRatio = width / height; if (aspectRatio === 1) { return { width: 1080, height: 1080, type: 'square' }; } else if (aspectRatio < 1) { const newHeight = Math.min(1350, height); const newWidth = Math.round(newHeight * aspectRatio); return { width: Math.min(1080, newWidth), height: newHeight, type: 'portrait' }; } else { const newWidth = Math.min(1080, width); const newHeight = Math.max(566, Math.round(newWidth / aspectRatio)); return { width: newWidth, height: newHeight, type: 'landscape' }; } } } module.exports = ImageProcessor;