Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 3x 3x 3x 3x 3x 3x 2x 2x 2x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x | // src/utils/imageProcessor.ts
import sharp from 'sharp';
import path from 'path';
import fs from 'node:fs/promises';
import type { Logger } from 'pino';
import { sanitizeFilename } from './stringUtils';
/**
* Processes an uploaded image file by stripping all metadata (like EXIF)
* and optimizing it for web use.
*
* @param sourcePath The path to the temporary uploaded file.
* @param destinationDir The directory where the final image should be saved.
* @param originalFileName The original name of the file, used to determine the output name.
* @param logger A pino logger instance for logging.
* @returns The file name of the newly processed image.
*/
export async function processAndSaveImage(
sourcePath: string,
destinationDir: string,
originalFileName: string,
logger: Logger,
): Promise<string> {
// Create a unique-ish filename to avoid collisions, but keep the original extension.
const fileExt = path.extname(originalFileName);
const fileBase = path.basename(originalFileName, fileExt);
const outputFileName = `${fileBase}-${Date.now()}${fileExt}`;
const outputPath = path.join(destinationDir, outputFileName);
try {
// Ensure the destination directory exists.
await fs.mkdir(destinationDir, { recursive: true });
logger.debug({ sourcePath, outputPath }, 'Starting image processing: stripping metadata and optimizing.');
// Use sharp to process the image.
// .withMetadata({}) strips all EXIF and other metadata.
// .jpeg() and .png() apply format-specific optimizations.
await sharp(sourcePath, { failOn: 'none' })
.withMetadata({}) // This is the key to stripping metadata
.jpeg({ quality: 85, mozjpeg: true }) // Optimize JPEGs
.png({ compressionLevel: 8, quality: 85 }) // Optimize PNGs
.toFile(outputPath);
logger.info(`Successfully processed image and saved to ${outputPath}`);
console.log('[DEBUG] processAndSaveImage returning:', outputFileName);
return outputFileName;
} catch (error) {
logger.error(
{ err: error, sourcePath, outputPath },
'An error occurred during image processing and saving.',
);
// Re-throw the error to be handled by the calling service (e.g., the worker).
throw new Error(`Failed to process image ${originalFileName}.`);
}
}
/**
* Generates a small WebP icon from a source image.
*
* @param sourcePath The path to the source image (can be the original upload or the processed image).
* @param outputDir The directory to save the icon in (e.g., 'flyer-images/icons').
* @param logger A pino logger instance for logging.
* @returns The file name of the generated icon.
*/
export async function generateFlyerIcon(
sourcePath: string,
outputDir: string,
logger: Logger,
): Promise<string> {
// Sanitize the base name of the source file to create a clean icon name.
const sourceBaseName = path.parse(sourcePath).name;
const iconFileName = `icon-${sanitizeFilename(sourceBaseName)}.webp`;
const outputPath = path.join(outputDir, iconFileName);
try {
// Ensure the output directory exists.
await fs.mkdir(outputDir, { recursive: true });
logger.debug({ sourcePath, outputPath }, 'Starting icon generation.');
await sharp(sourcePath, { failOn: 'none' })
.resize({ width: 128, height: 128, fit: 'inside' })
.webp({ quality: 75 }) // Slightly lower quality for icons is acceptable.
.toFile(outputPath);
logger.info(`Successfully generated icon: ${outputPath}`);
console.log('[DEBUG] generateFlyerIcon returning:', iconFileName);
return iconFileName;
} catch (error) {
logger.error(
{ err: error, sourcePath, outputPath },
'An error occurred during icon generation.',
);
// Re-throw the error to be handled by the calling service.
throw new Error(`Failed to generate icon for ${sourcePath}.`);
}
} |