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
JavaScript
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;