import sharp from "sharp"; import { getMinioClient } from "./minio"; import { lookup } from "mime-types"; export async function processImage( bucket: string, key: string ): Promise { const minio = getMinioClient(); // Fetch metadata const stat = await minio.statObject(bucket, key); const meta = stat as unknown as { metaData: Record }; const mime = meta.metaData["content-type"] || lookup(key) || ""; if (!mime.startsWith("image/")) { console.log(`⏭️ Skipping non-image file: ${key}`); return false; } if ( meta.metaData["x-amz-meta-processed"] === "true" || meta.metaData["processed"] === "true" ) { console.log(`♻️ Already processed: ${key}`); return false; } const originalStream = await minio.getObject(bucket, key); const fileName = key.split("/").pop()!; const filePath = key.substring(0, key.lastIndexOf("/")); const processedMeta = { "x-amz-meta-processed": "true", }; // Helper to upload from a stream async function uploadFromStream( targetPath: string, mimeType: string, transformStream: NodeJS.ReadableStream ) { const chunks: Buffer[] = []; for await (const chunk of transformStream) { chunks.push(chunk as Buffer); } const finalBuffer = Buffer.concat(chunks); await minio.putObject(bucket, targetPath, finalBuffer, finalBuffer.length, { "Content-Type": mimeType, ...processedMeta, }); } try { // 🖼️ Thumbnail (resize to 200px width) await uploadFromStream( `${filePath}/thumbs/${fileName}`, mime, originalStream.pipe(sharp().resize(200)) ); // Re-fetch original again for each variant (streams are one-time-use) const optimizedStream = await minio.getObject(bucket, key); // 📸 Optimized JPEG await uploadFromStream( `${filePath}/optimized/${fileName}`, "image/jpeg", optimizedStream.pipe(sharp().jpeg({ quality: 80 })) ); const webpStream = await minio.getObject(bucket, key); // 🌐 WebP version const webpName = fileName.replace(/\.[^/.]+$/, ".webp"); await uploadFromStream( `${filePath}/webp/${webpName}`, "image/webp", webpStream.pipe(sharp().webp({ quality: 80 })) ); const finalOriginalStream = await minio.getObject(bucket, key); // 🔁 Re-upload the original with updated metadata to mark it processed await uploadFromStream(key, mime, finalOriginalStream); console.log(`✅ Processed image: ${key}`); return true; } catch (err) { console.error(`❌ Error processing image (${key}):`, err); return false; } }