Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 58 additions & 59 deletions modules/shared_resources/src/img_resizing/index.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,80 @@
// dependencies
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { Readable } from "stream";
import sharp from "sharp";
import util from "util";
const { S3Client, GetObjectCommand, PutObjectCommand } = require("@aws-sdk/client-s3");
const sharp = require("sharp");
const util = require("util");

// create S3 client
const s3 = new S3Client({ region: "ap-northeast-2" });
const SOURCE_PREFIX = "original/";
const DESTINATION_PREFIX = "resize/";
const SUPPORTED_IMAGE_TYPES = new Set(["jpg", "jpeg", "png"]);

// define the handler function
export const handler = async (event, context) => {
// Read options from the event parameter and get the source bucket
exports.handler = async (event) => {
console.log("Reading options from event:\n", util.inspect(event, { depth: 5 }));
const srcBucket = event.Records[0].s3.bucket.name;

// Object key may have spaces or unicode non-ASCII characters
const srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));
const dstBucket = srcBucket; // Destination bucket is the same as source bucket
const dstKey = srcKey.replace("original", "resize").replace(/\.\w+$/, ".webp"); // Change directory and file extension
const records = event?.Records ?? [];
if (records.length === 0) {
console.log("No S3 records found. Skipping.");
return;
}

for (const record of records) {
await resizeImage(record);
}
};

async function resizeImage(record) {
const srcBucket = record?.s3?.bucket?.name;
const objectKey = record?.s3?.object?.key;

if (!srcBucket || !objectKey) {
console.log("Invalid S3 record. Skipping.");
return;
}

const srcKey = decodeURIComponent(objectKey.replace(/\+/g, " "));
if (!srcKey.startsWith(SOURCE_PREFIX)) {
console.log(`Not an original image. Skipping: ${srcKey}`);
return;
}

// Infer the image type from the file suffix
const typeMatch = srcKey.match(/\.([^.]*)$/);
if (!typeMatch) {
console.log("Could not determine the image type.");
console.log(`Could not determine the image type: ${srcKey}`);
return;
}

// Supported image types for Sharp
const imageType = typeMatch[1].toLowerCase();
if (imageType != "jpg" && imageType != "png") {
if (!SUPPORTED_IMAGE_TYPES.has(imageType)) {
console.log(`Unsupported image type: ${imageType}`);
return;
}

try {
const params = {
Bucket: srcBucket,
Key: srcKey,
};
var response = await s3.send(new GetObjectCommand(params));
var stream = response.Body;
const dstKey = srcKey.replace(SOURCE_PREFIX, DESTINATION_PREFIX).replace(/\.[^.]+$/, ".webp");

if (stream instanceof Readable) {
var content_buffer = Buffer.concat(await stream.toArray());
} else {
throw new Error("Unknown object stream type");
}
} catch (error) {
console.log(error);
return;
}
const response = await s3.send(new GetObjectCommand({
Bucket: srcBucket,
Key: srcKey,
}));

const width = 600; // set thumbnail width
const contentBuffer = await streamToBuffer(response.Body);
const outputBuffer = await sharp(contentBuffer)
.resize(600)
.webp()
.toBuffer();

try {
var output_buffer = await sharp(content_buffer)
.resize(width)
.webp() // Convert to webp
.toBuffer();
} catch (error) {
console.log(error);
return;
}
await s3.send(new PutObjectCommand({
Bucket: srcBucket,
Key: dstKey,
Body: outputBuffer,
ContentType: "image/webp",
}));

// Upload the resized .webp image to the destination bucket
try {
const destparams = {
Bucket: dstBucket,
Key: dstKey,
Body: output_buffer,
ContentType: "image/webp", // Set the correct Content-Type
};
console.log(`Successfully resized ${srcBucket}/${srcKey} and uploaded to ${srcBucket}/${dstKey}`);
}

await s3.send(new PutObjectCommand(destparams));
} catch (error) {
console.log(error);
return;
async function streamToBuffer(stream) {
const chunks = [];
for await (const chunk of stream) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}

console.log("Successfully resized " + srcBucket + "/" + srcKey + " and uploaded to " + dstBucket + "/" + dstKey);
};
return Buffer.concat(chunks);
}
194 changes: 95 additions & 99 deletions modules/shared_resources/src/thumbnail/index.js
Original file line number Diff line number Diff line change
@@ -1,108 +1,104 @@
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';

const s3Client = new S3Client({ region: 'ap-northeast-2' });

export const handler = async (event) => {
console.log('Event received:', JSON.stringify(event, null, 2));

try {
// S3 이벤트에서 버킷과 객체 정보 추출
const record = event.Records[0];
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));

console.log(`Processing file: ${key} from bucket: ${bucket}`);

// chat/files/ 폴더의 이미지 파일만 처리
if (!key.startsWith('chat/files/')) {
console.log('Not a chat file, skipping');
return { statusCode: 200, body: 'Not a chat file' };
}

// 이미지 파일 확장자 확인
const imageExtensions = ['.jpg', '.jpeg', '.png', '.webp'];
if (!imageExtensions.some(ext => key.toLowerCase().endsWith(ext))) {
console.log('Not an image file, skipping');
return { statusCode: 200, body: 'Not an image file' };
}

// 이미 썸네일 파일인 경우 처리하지 않음 (무한 루프 방지)
if (key.includes('_thumb')) {
console.log('Already a thumbnail, skipping');
return { statusCode: 200, body: 'Already a thumbnail' };
}

// 원본 이미지 다운로드 (AWS SDK v3 방식)
console.log('Downloading original image...');
const getCommand = new GetObjectCommand({ Bucket: bucket, Key: key });
const response = await s3Client.send(getCommand);

// Body를 Buffer로 변환
const imageBuffer = await streamToBuffer(response.Body);

// Sharp를 사용해 썸네일 생성
console.log('Creating thumbnail...');
const thumbnailBuffer = await sharp(imageBuffer)
.resize(200, 200, {
fit: 'inside', // 비율 유지하면서 200x200 안에 맞춤
withoutEnlargement: true // 원본보다 크게 만들지 않음
})
.jpeg({ quality: 85 }) // JPEG 품질 85%
.toBuffer();

// 썸네일 파일명 생성
const fileName = key.split('/').pop(); // 파일명만 추출
const nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.'));
const extension = fileName.substring(fileName.lastIndexOf('.'));
const thumbnailKey = `chat/thumbnails/${nameWithoutExt}_thumb${extension}`;

console.log(`Uploading thumbnail to: ${thumbnailKey}`);

// 썸네일을 S3에 업로드 (AWS SDK v3 방식)
const putCommand = new PutObjectCommand({
Bucket: bucket,
Key: thumbnailKey,
Body: thumbnailBuffer,
ContentType: 'image/jpeg',
Metadata: {
'original-key': key,
'generated-by': 'thumbnail-lambda'
}
});

await s3Client.send(putCommand);

console.log(`Thumbnail created successfully: ${thumbnailKey}`);

return {
statusCode: 200,
body: JSON.stringify({
message: 'Thumbnail created successfully',
original: key,
thumbnail: thumbnailKey,
thumbnailSize: thumbnailBuffer.length
})
};

} catch (error) {
console.error('Error creating thumbnail:', error);

return {
statusCode: 500,
body: JSON.stringify({
message: 'Error creating thumbnail',
error: error.message
})
};
const { S3Client, GetObjectCommand, PutObjectCommand } = require("@aws-sdk/client-s3");
const sharp = require("sharp");

const s3 = new S3Client({ region: "ap-northeast-2" });
const SOURCE_PREFIX = "chat/files/";
const THUMBNAIL_PREFIX = "chat/thumbnails/";
const SUPPORTED_IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".webp"]);

exports.handler = async (event) => {
console.log("Event received:", JSON.stringify(event, null, 2));

const records = event?.Records ?? [];
if (records.length === 0) {
console.log("No S3 records found. Skipping.");
return { statusCode: 200, body: "No S3 records found" };
}

const results = [];
for (const record of records) {
results.push(await createThumbnail(record));
}

return {
statusCode: 200,
body: JSON.stringify({ results }),
};
};

// Stream을 Buffer로 변환하는 헬퍼 함수
async function createThumbnail(record) {
const bucket = record?.s3?.bucket?.name;
const objectKey = record?.s3?.object?.key;

if (!bucket || !objectKey) {
console.log("Invalid S3 record. Skipping.");
return { skipped: true, reason: "Invalid S3 record" };
}

const key = decodeURIComponent(objectKey.replace(/\+/g, " "));
console.log(`Processing file: ${key} from bucket: ${bucket}`);

if (!key.startsWith(SOURCE_PREFIX)) {
console.log("Not a chat file, skipping");
return { skipped: true, reason: "Not a chat file", key };
}

const extension = getExtension(key);
if (!SUPPORTED_IMAGE_EXTENSIONS.has(extension)) {
console.log("Not an image file, skipping");
return { skipped: true, reason: "Not an image file", key };
}

if (key.includes("_thumb")) {
console.log("Already a thumbnail, skipping");
return { skipped: true, reason: "Already a thumbnail", key };
}

const response = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const imageBuffer = await streamToBuffer(response.Body);

const thumbnailBuffer = await sharp(imageBuffer)
.resize(200, 200, {
fit: "inside",
withoutEnlargement: true,
})
.jpeg({ quality: 85 })
.toBuffer();

const fileName = key.split("/").pop();
const nameWithoutExt = fileName.slice(0, -extension.length);
const thumbnailKey = `${THUMBNAIL_PREFIX}${nameWithoutExt}_thumb.jpg`;

await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: thumbnailKey,
Body: thumbnailBuffer,
ContentType: "image/jpeg",
Metadata: {
"original-key": key,
"generated-by": "thumbnail-lambda",
},
}));

console.log(`Thumbnail created successfully: ${thumbnailKey}`);

return {
skipped: false,
original: key,
thumbnail: thumbnailKey,
thumbnailSize: thumbnailBuffer.length,
};
}

function getExtension(key) {
const dotIndex = key.lastIndexOf(".");
return dotIndex === -1 ? "" : key.slice(dotIndex).toLowerCase();
}

async function streamToBuffer(stream) {
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks);
}
Loading