Skip to content

Commit 29cf437

Browse files
committed
Fixed: uploading large and raw images
1 parent 778c7a2 commit 29cf437

File tree

4 files changed

+86
-79
lines changed

4 files changed

+86
-79
lines changed

src/app/api/modify/route.ts

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface ImageInfo {
1010
raw: boolean;
1111
}
1212

13-
interface ProcessedImage {
13+
interface ModifiedImage {
1414
title: string;
1515
previewUrl: string;
1616
sizeKb: number;
@@ -24,7 +24,7 @@ interface ProcessedImage {
2424
export async function POST(req: NextRequest) {
2525
try {
2626
const formData = await req.formData();
27-
const processedImages: ProcessedImage[] = [];
27+
const modifiedImages: ModifiedImage[] = [];
2828

2929
// Iterate through formData entries
3030
for (const [key, value] of formData.entries()) {
@@ -35,42 +35,45 @@ export async function POST(req: NextRequest) {
3535
formData.get(`modification${index}`) as string,
3636
) as ImageInfo;
3737

38-
const processedImage = await processImage(imageFile, imageInfo);
39-
processedImages.push(processedImage);
38+
const modifiedImage = await modifyImage(imageFile, imageInfo);
39+
modifiedImages.push(modifiedImage);
4040
}
4141
}
4242

43-
return NextResponse.json(processedImages);
43+
return NextResponse.json(modifiedImages);
4444
} catch (error) {
45-
console.error("Error processing images:", error);
45+
console.error("Error modifying images:", error);
4646
return NextResponse.json(
47-
{ error: "Failed to process images" },
47+
{ error: "Failed to modify images" },
4848
{ status: 500 },
4949
);
5050
}
5151
}
5252

53-
async function processImage(
53+
async function modifyImage(
5454
imageFile: File,
5555
imageInfo: ImageInfo,
56-
): Promise<ProcessedImage> {
56+
): Promise<ModifiedImage> {
5757
const fileBuffer = await imageFile.arrayBuffer();
58-
const image = sharp(Buffer.from(fileBuffer), { animated: false });
5958

60-
const metadata = await image.metadata();
6159
const raw = imageInfo.raw;
6260
const outputTitle = imageInfo.title;
6361
let outputWidth;
6462
let outputHeight;
6563
let outputFormat;
6664
let quality;
65+
let imageBuffer;
66+
let modifiedInfo;
6767

6868
if (raw) {
69-
outputHeight = metadata.height;
70-
outputWidth = metadata.width;
71-
outputFormat = metadata.format;
72-
quality = 100;
69+
imageBuffer = fileBuffer;
70+
outputHeight = imageInfo.height!;
71+
outputWidth = imageInfo.width!;
72+
outputFormat = imageInfo.format;
7373
} else {
74+
const image = sharp(Buffer.from(fileBuffer), { animated: false });
75+
const metadata = await image.metadata();
76+
7477
if (!imageInfo.height && !imageInfo.width) {
7578
outputHeight = metadata.height;
7679
outputWidth = metadata.width;
@@ -83,28 +86,37 @@ async function processImage(
8386

8487
outputFormat = imageInfo.format ?? metadata.format;
8588
quality = imageInfo.quality;
86-
}
8789

88-
if (!raw) {
8990
image
9091
.resize(outputWidth, outputHeight)
9192
.toFormat(outputFormat as keyof sharp.FormatEnum, {
9293
quality,
9394
});
94-
}
9595

96-
const { data: processedImageBuffer, info } = await image.toBuffer({
97-
resolveWithObject: true,
98-
});
96+
const {
97+
data,
98+
info,
99+
}: {
100+
data: Buffer;
101+
info: { height: number; width: number; format: string };
102+
} = await image.toBuffer({
103+
resolveWithObject: true,
104+
});
105+
106+
modifiedInfo = info;
107+
imageBuffer = data;
108+
109+
outputHeight = modifiedInfo.height;
110+
outputWidth = modifiedInfo.width;
111+
outputFormat = modifiedInfo.format;
112+
}
99113

100-
outputHeight = info.height;
101-
outputWidth = info.width;
102-
outputFormat = info.format;
114+
const buffer = Buffer.from(imageBuffer).toString("base64");
103115

104116
return {
105117
title: outputTitle,
106-
previewUrl: `data:image/${outputFormat};base64,${processedImageBuffer.toString("base64")}`,
107-
sizeKb: processedImageBuffer.length / 1024,
118+
previewUrl: `data:image/${outputFormat};base64,${buffer}`,
119+
sizeKb: imageBuffer.byteLength / 1024,
108120
format: outputFormat,
109121
height: outputHeight,
110122
width: outputWidth,

src/app/api/upload/route.ts

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { type NextRequest, NextResponse } from "next/server";
22
import { s3Client } from "@/lib/s3-client";
33
import { PutObjectCommand } from "@aws-sdk/client-s3";
44

5-
interface ImageUploadInfo {
5+
interface Info {
66
title: string;
77
format: string;
8-
previewUrl: string;
98
bucket: string;
109
folder: string;
1110
}
@@ -17,9 +16,13 @@ export async function POST(req: NextRequest) {
1716
const uploadResults: Array<{ imageName: string; imageUrl: string }> = [];
1817

1918
for (const [key, value] of formData.entries()) {
20-
if (key.startsWith("processedImage")) {
21-
const imageUploadInfo = JSON.parse(value as string) as ImageUploadInfo;
22-
const uploadResult = await uploadImageToS3(imageUploadInfo);
19+
if (key.startsWith("info")) {
20+
const index = key.replace("info", "");
21+
22+
const info = JSON.parse(value as string) as Info;
23+
const image = formData.get(`image${index}`) as File;
24+
25+
const uploadResult = await uploadImageToS3(image, info);
2326

2427
uploadResults.push(uploadResult);
2528
}
@@ -43,40 +46,29 @@ export async function POST(req: NextRequest) {
4346
}
4447

4548
async function uploadImageToS3(
46-
imageUploadInfo: ImageUploadInfo,
49+
imageFile: File,
50+
info: Info,
4751
): Promise<{ imageName: string; imageUrl: string }> {
48-
const imageFile = base64ToFile(
49-
imageUploadInfo.previewUrl,
50-
imageUploadInfo.title,
51-
);
52-
5352
let bucketName;
5453
let key;
5554

5655
// NEXT_PUBLIC_AWS_S3_STORAGE_BUCKET_NAME is optional
5756
if (
5857
!process.env.NEXT_PUBLIC_AWS_S3_STORAGE_BUCKET_NAME ||
59-
imageUploadInfo.bucket === "server"
58+
info.bucket === "server"
6059
) {
6160
bucketName = process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME;
62-
key = `assets/${imageUploadInfo.folder}/${imageUploadInfo.title}`.replace(
63-
/\/\//g,
64-
"/",
65-
);
61+
key = `assets/${info.folder}/${info.title}`.replace(/\/\//g, "/");
6662
} else {
6763
bucketName = process.env.NEXT_PUBLIC_AWS_S3_STORAGE_BUCKET_NAME;
68-
key =
69-
`images/assets/${imageUploadInfo.folder}/${imageUploadInfo.title}`.replace(
70-
/\/\//g,
71-
"/",
72-
);
64+
key = `images/assets/${info.folder}/${info.title}`.replace(/\/\//g, "/");
7365
}
7466

7567
const uploadParams = {
7668
Bucket: bucketName,
7769
Key: key,
7870
Body: Buffer.from(await imageFile.arrayBuffer()),
79-
ContentType: `image/${imageUploadInfo.format}`,
71+
ContentType: `image/${info.format}`,
8072
CacheControl: "max-age=31536000",
8173
};
8274

@@ -89,31 +81,3 @@ async function uploadImageToS3(
8981
imageUrl,
9082
};
9183
}
92-
93-
function base64ToFile(base64String: string, fileName: string): File {
94-
const [mimePart, dataPart] = base64String.split(",");
95-
96-
if (!mimePart || !dataPart) {
97-
throw new Error("Invalid base64 string");
98-
}
99-
100-
const mimeTypeMatch = /:(.*?);/.exec(mimePart);
101-
102-
if (!mimeTypeMatch?.[1]) {
103-
throw new Error("Invalid MIME type");
104-
}
105-
106-
const mimeType = mimeTypeMatch[1];
107-
108-
const byteCharacters = atob(dataPart);
109-
const byteNumbers = new Array(byteCharacters.length);
110-
111-
for (let i = 0; i < byteCharacters.length; i++) {
112-
byteNumbers[i] = byteCharacters.charCodeAt(i);
113-
}
114-
115-
const byteArray = new Uint8Array(byteNumbers);
116-
const blob = new Blob([byteArray], { type: mimeType });
117-
118-
return new File([blob], fileName, { type: mimeType });
119-
}

src/app/page.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ImageModifyForm, {
1111
} from "@/components/image-modify-form";
1212
import { ChevronLeft, Loader } from "lucide-react";
1313
import Header from "@/components/header";
14+
import { base64ToFile } from "@/lib/utils";
1415

1516
interface UploadFormData {
1617
title: string;
@@ -151,7 +152,7 @@ export default function HomePage() {
151152
onSuccess: (data) => {
152153
setProcessedImages(data);
153154
setStep(2);
154-
toast.success("Images processed successfully! Now upload them.");
155+
toast.success("Images were modified successfully! Now upload them.");
155156
},
156157
onError: (error: unknown) => {
157158
if (error instanceof Error) {
@@ -217,13 +218,16 @@ export default function HomePage() {
217218
const form = uploadForms[index];
218219

219220
if (form) {
221+
const { previewUrl, ...rest } = image;
220222
const requestData = {
221-
...image,
223+
...rest,
222224
folder: form.folder,
223225
title: form.title,
224226
bucket: form.bucket,
225227
};
226-
formData.append(`processedImage${index}`, JSON.stringify(requestData));
228+
229+
formData.append(`image${index}`, base64ToFile(previewUrl, form.title));
230+
formData.append(`info${index}`, JSON.stringify(requestData));
227231
}
228232
});
229233

src/lib/utils.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,30 @@ export function getFileFormat(file: File): string {
2727

2828
throw new Error("Unable to determine file format from MIME type.");
2929
}
30+
31+
export function base64ToFile(base64: string, filename: string): File {
32+
const base64Parts = base64.split(",");
33+
34+
if (base64Parts.length < 2) {
35+
throw new Error("Invalid base64 string");
36+
}
37+
38+
const byteString = atob(base64Parts[1]!);
39+
40+
const mimeParts = base64Parts[0]!.split(":");
41+
if (mimeParts.length < 2) {
42+
throw new Error("Invalid MIME type in base64 string");
43+
}
44+
45+
const mimeString = mimeParts[1]!.split(";")[0];
46+
47+
const byteNumbers = new Array(byteString.length);
48+
for (let i = 0; i < byteString.length; i++) {
49+
byteNumbers[i] = byteString.charCodeAt(i);
50+
}
51+
52+
const byteArray = new Uint8Array(byteNumbers);
53+
54+
const blob = new Blob([byteArray], { type: mimeString });
55+
return new File([blob], filename, { type: mimeString });
56+
}

0 commit comments

Comments
 (0)