// worker/process-image.ts 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(); 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 stream = await minio.getObject(bucket, key); const chunks: Buffer[] = []; for await (const chunk of stream) chunks.push(chunk); const buffer = Buffer.concat(chunks); const fileName = key.split("/").pop(); const filePath = key.substring(0, key.lastIndexOf("/")); const thumb = await sharp(buffer).resize(200).toBuffer(); await minio.putObject( bucket, `${filePath}/thumbs/${fileName}`, thumb, thumb.length, { "Content-Type": "image/jpeg", "x-amz-meta-processed": "true", } ); const optimized = await sharp(buffer).jpeg({ quality: 80 }).toBuffer(); await minio.putObject( bucket, `${filePath}/optimized/${fileName}`, optimized, optimized.length, { "Content-Type": "image/jpeg", "x-amz-meta-processed": "true", } ); // Re-upload original object with metadata await minio.putObject(bucket, key, buffer, buffer.length, { "Content-Type": mime, "x-amz-meta-processed": "true", }); console.log(`✅ Processed image: ${key}`); return true; }