minio-worker/process-image.ts

83 lines
2.0 KiB
TypeScript

// 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<boolean> {
const minio = getMinioClient();
const stat = await minio.statObject(bucket, key);
const meta = stat as unknown as { metaData: Record<string, string> };
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": mime,
"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": mime,
"x-amz-meta-processed": "true",
}
);
const webp = await sharp(buffer).webp({ quality: 80 }).toBuffer();
await minio.putObject(
bucket,
`${filePath}/webp/${fileName?.replace(/\.[^/.]+$/, ".webp")}`,
webp,
webp.length,
{
"Content-Type": "image/webp",
"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;
}