89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
import sharp from "sharp";
|
|
import { getMinioClient } from "./minio";
|
|
import { lookup } from "mime-types";
|
|
import { sleepWithCountdown } from "./util";
|
|
|
|
export async function processImage(
|
|
bucket: string,
|
|
key: string
|
|
): Promise<boolean> {
|
|
const minio = getMinioClient();
|
|
|
|
// Get metadata
|
|
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) || "";
|
|
|
|
// Skip if not an image
|
|
if (!mime.startsWith("image/")) {
|
|
console.log(`⏭️ Skipping non-image file: ${key}`);
|
|
return false;
|
|
}
|
|
|
|
// Skip if already processed
|
|
if (
|
|
meta.metaData["x-amz-meta-processed"] === "true" ||
|
|
meta.metaData["processed"] === "true"
|
|
) {
|
|
console.log(`♻️ Already processed: ${key}`);
|
|
return false;
|
|
}
|
|
|
|
// Read original image
|
|
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 processedMeta = {
|
|
"x-amz-meta-processed": "true",
|
|
};
|
|
|
|
// Helper function to write to MinIO
|
|
async function writeImage(path: string, buffer: Buffer, mimeType: string) {
|
|
await minio.putObject(bucket, path, buffer, buffer.length, {
|
|
"Content-Type": mimeType,
|
|
...processedMeta,
|
|
});
|
|
}
|
|
|
|
try {
|
|
await sleepWithCountdown(5);
|
|
|
|
// 🖼️ Create thumbnail
|
|
const thumb = await sharp(buffer).resize(200).toBuffer();
|
|
await writeImage(`${filePath}/thumbs/${fileName}`, thumb, mime);
|
|
|
|
// 📸 Optimized JPEG
|
|
const optimized = await sharp(buffer).jpeg({ quality: 80 }).toBuffer();
|
|
await writeImage(
|
|
`${filePath}/optimized/${fileName}`,
|
|
optimized,
|
|
"image/jpeg"
|
|
);
|
|
|
|
// 🌐 WebP variant
|
|
const webpName = fileName?.replace(/\.[^/.]+$/, ".webp");
|
|
const webp = await sharp(buffer).webp({ quality: 80 }).toBuffer();
|
|
await writeImage(`${filePath}/webp/${webpName}`, webp, "image/webp");
|
|
|
|
// (Optional: AVIF format - super modern)
|
|
// const avifName = fileName?.replace(/\.[^/.]+$/, ".avif");
|
|
// const avif = await sharp(buffer).avif({ quality: 50 }).toBuffer();
|
|
// await writeImage(`${filePath}/avif/${avifName}`, avif, "image/avif");
|
|
|
|
// 🔁 Re-upload original with metadata to mark as processed
|
|
await writeImage(key, buffer, mime);
|
|
|
|
console.log(`✅ Processed image: ${key}`);
|
|
return true;
|
|
} catch (err) {
|
|
console.error(`❌ Error processing image (${key}):`, err);
|
|
return false;
|
|
}
|
|
}
|