jn-pdf
Version:
This little command line utility scans pwd and merges all image files sorted by name into a single PDF file.
148 lines (122 loc) • 4.85 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { PDFDocument } = require('pdf-lib');
const glob = require('glob');
const sharp = require('sharp');
const { Command } = require('commander');
const rimraf = require('rimraf');
const cacheManager = require('cache-manager');
const packageJson = require('./package.json');
// Initialize Commander for CLI Options
const program = new Command();
program
.version(packageJson.version)
.description('Merge images and PDFs into a single PDF file named after the current folder')
.option('-v, --verbose', 'Display detailed processing information')
.option('--scale <factor>', 'Scale factor for images (default: 1)', parseFloat, 1)
.parse(process.argv);
// Initialize cache-manager with in-memory cache
const cache = cacheManager.caching({ store: 'memory', max: 100, ttl: 10 /*seconds*/ });
// Utility Function to Log Messages When Verbose Mode is Enabled
const log = (...args) => {
if (program.verbose) {
console.log(...args);
}
};
// Utility Function to Get Cached Image
async function getCachedImage(filePath) {
return new Promise((resolve, reject) => {
cache.get(filePath, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
}
// Utility Function to Set Cached Image
async function setCachedImage(filePath, imageBuffer) {
return new Promise((resolve, reject) => {
cache.set(filePath, imageBuffer, { ttl: 10 }, (err) => {
if (err) return reject(err);
resolve();
});
});
}
// Wrap glob in a promise
const getFiles = (pattern, options) => {
return new Promise((resolve, reject) => {
glob(pattern, options, (err, files) => {
if (err) return reject(err);
resolve(files);
});
});
};
// Main Async Function
(async () => {
try {
const pwd = process.cwd();
const folderName = path.basename(pwd);
const scaleFactor = program.scale || 1; // Scale factor for images
console.log(`\nDescription: ${program.description()}`);
console.log(`Version: ${program.version()}`);
console.log(`Working Directory: ${pwd}`);
console.log(`Output File Name: ${folderName}.pdf`);
console.log(`Verbose Mode: ${program.verbose ? 'Enabled' : 'Disabled'}\n`);
// Use the custom getFiles function to get files with a promise
const files = await getFiles('*.{png,jpg,jpeg,gif,pdf}', { cwd: pwd, absolute: true });
if (!Array.isArray(files) || files.length === 0) {
console.log('No image or PDF files found in the current directory.');
return;
}
log('Files Found:', files);
const pdfDoc = await PDFDocument.create();
for (const filePath of files) {
const extension = path.extname(filePath).toLowerCase();
if (extension === '.pdf') {
log(`Processing PDF: ${filePath}`);
const existingPdfBytes = fs.readFileSync(filePath);
const existingPdfDoc = await PDFDocument.load(existingPdfBytes);
const copiedPages = await pdfDoc.copyPages(existingPdfDoc, existingPdfDoc.getPageIndices());
copiedPages.forEach((page) => pdfDoc.addPage(page));
} else {
try {
log(`Processing Image: ${filePath}`);
// Check Cache First
let imageBuffer = await getCachedImage(filePath);
if (!imageBuffer) {
const image = sharp(filePath);
const metadata = await image.metadata();
imageBuffer = await image
.resize({
width: Math.floor(metadata.width * scaleFactor),
height: Math.floor(metadata.height * scaleFactor),
fit: 'inside'
})
.jpeg()
.toBuffer();
// Store in Cache
await setCachedImage(filePath, imageBuffer);
}
const jpgImage = await pdfDoc.embedJpg(imageBuffer);
const dimensions = jpgImage.scale(scaleFactor); // Apply scaling
const page = pdfDoc.addPage([dimensions.width, dimensions.height]);
page.drawImage(jpgImage, {
x: 0,
y: 0,
width: dimensions.width,
height: dimensions.height,
});
log(`Successfully processed image: ${filePath}`);
} catch (error) {
console.error(`Failed to process image: ${filePath}. Error: ${error.message}`);
}
}
}
const pdfBytes = await pdfDoc.save();
const outputFilePath = path.join(pwd, `${folderName}.pdf`);
fs.writeFileSync(outputFilePath, pdfBytes);
console.log(`\nPDF successfully created at: ${outputFilePath}`);
} catch (error) {
console.error('An error occurred:', error.message);
}
})();