파일을 다운로드할 때 전체 파일을 메모리에 올리는 대신 서비스 워커에서 스트리밍으로 복호화하도록 변경

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
staticandClaude Opus 5.5 committed 2026-10-12 00:48:21 +09:00
1 parent 877cf9c42e
commit 3e52fa7a7c
29 files changed
+629 -467

No files matched your search

+20
View File
@@ -25,3 +25,23 @@ export const extractExifDateTime = (fileBuffer: ArrayBuffer) => {
const offsetMs = offsetSign * ((offsetHour ?? 0) * 60 + (offsetMinute ?? 0)) * 60 * 1000;
return new Date(utcDate - offsetMs);
};
const EXIF_SEARCH_SIZE = 1024 * 1024; // 1 MiB
const MAX_FULL_READ_SIZE = 64 * 1024 * 1024; // 64 MiB
const tryExtractExifDateTime = (buffer: ArrayBuffer) => {
try {
return extractExifDateTime(buffer);
} catch {
return undefined;
}
};
// EXIF is usually at the beginning of the file, so the whole file is read only if not found
export const readExifDateTime = async (file: Blob) => {
const dateTime = tryExtractExifDateTime(await file.slice(0, EXIF_SEARCH_SIZE).arrayBuffer());
if (dateTime || file.size <= EXIF_SEARCH_SIZE || file.size > MAX_FULL_READ_SIZE) {
return dateTime;
}
return tryExtractExifDateTime(await file.arrayBuffer());
};
+24 -17
View File
@@ -82,35 +82,42 @@ const generateVideoThumbnail = (videoUrl: string, time = 0) => {
});
};
export const generateThumbnail = async (blob: Blob) => {
let url;
const convertHeicToPng = async (blob: Blob) => {
const { default: heic2any } = await import("heic2any");
return (await heic2any({ blob, toType: "image/png" })) as Blob;
};
// Generates a thumbnail of the image or video at the URL. Returns `null` if not supported.
export const generateThumbnailFromUrl = async (url: string, contentType: string) => {
try {
if (blob.type.startsWith("image/")) {
url = URL.createObjectURL(blob);
if (contentType.startsWith("image/")) {
try {
return await generateImageThumbnail(url);
} catch {
URL.revokeObjectURL(url);
url = undefined;
if (contentType !== "image/heic") return null;
if (blob.type === "image/heic") {
const { default: heic2any } = await import("heic2any");
url = URL.createObjectURL((await heic2any({ blob, toType: "image/png" })) as Blob);
return await generateImageThumbnail(url);
} else {
return null;
// Chromium can't decode HEIC images
const png = URL.createObjectURL(await convertHeicToPng(await (await fetch(url)).blob()));
try {
return await generateImageThumbnail(png);
} finally {
URL.revokeObjectURL(png);
}
}
} else if (blob.type.startsWith("video/")) {
url = URL.createObjectURL(blob);
} else if (contentType.startsWith("video/")) {
return await generateVideoThumbnail(url);
}
return null;
} catch {
return null;
}
};
export const generateThumbnail = async (blob: Blob) => {
const url = URL.createObjectURL(blob);
try {
return await generateThumbnailFromUrl(url, blob.type);
} finally {
if (url) {
URL.revokeObjectURL(url);
}
URL.revokeObjectURL(url);
}
};