From 4679b1d6bdefe9bde7c10f72e93748319e2a4555 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 05:39:39 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8F=99=EC=98=81=EC=83=81=EC=9D=98=20?= =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=B4=20=EA=B0=80=EB=81=94=20?= =?UTF-8?q?=ED=9D=B0=EC=83=89=EC=9C=BC=EB=A1=9C=20=EC=9E=98=EB=AA=BB=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/file/cache.ts | 4 +- src/lib/modules/thumbnail.ts | 77 +++++++++++++++++------------------ 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/src/lib/modules/file/cache.ts b/src/lib/modules/file/cache.ts index 31eac28..ccb187e 100644 --- a/src/lib/modules/file/cache.ts +++ b/src/lib/modules/file/cache.ts @@ -54,9 +54,7 @@ export const deleteFileCache = async (fileId: number) => { export const getFileThumbnailCache = async (fileId: number) => { const thumbnail = loadedThumbnails.get(fileId); - if (thumbnail) { - return thumbnail; - } + if (thumbnail) return thumbnail; const thumbnailBuffer = await readFile(`/thumbnail/file/${fileId}`); if (!thumbnailBuffer) return null; diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts index 873772e..9c009bd 100644 --- a/src/lib/modules/thumbnail.ts +++ b/src/lib/modules/thumbnail.ts @@ -12,50 +12,25 @@ const scaleSize = (width: number, height: number, targetSize: number) => { }; }; -const generateImageThumbnail = (imageUrl: string) => { - return new Promise((resolve, reject) => { - const image = new Image(); - image.onload = () => { - const canvas = document.createElement("canvas"); - const { width, height } = scaleSize(image.width, image.height, 250); - - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext("2d"); - if (!context) { - return reject(new Error("Failed to generate thumbnail")); - } - - context.drawImage(image, 0, 0, width, height); - canvas.toBlob((blob) => { - if (blob) { - resolve(blob); - } else { - reject(new Error("Failed to generate thumbnail")); - } - }, "image/webp"); - }; - image.onerror = reject; - - image.src = imageUrl; - }); -}; - -export const captureVideoThumbnail = (video: HTMLVideoElement) => { +const capture = ( + width: number, + height: number, + drawer: (context: CanvasRenderingContext2D, width: number, height: number) => void, + targetSize = 250, +) => { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); - const { width, height } = scaleSize(video.videoWidth, video.videoHeight, 250); + const { width: scaledWidth, height: scaledHeight } = scaleSize(width, height, targetSize); - canvas.width = width; - canvas.height = height; + canvas.width = scaledWidth; + canvas.height = scaledHeight; const context = canvas.getContext("2d"); if (!context) { return reject(new Error("Failed to generate thumbnail")); } - context.drawImage(video, 0, 0, width, height); + drawer(context, scaledWidth, scaledHeight); canvas.toBlob((blob) => { if (blob) { resolve(blob); @@ -66,14 +41,36 @@ export const captureVideoThumbnail = (video: HTMLVideoElement) => { }); }; +const generateImageThumbnail = (imageUrl: string) => { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => { + capture(image.width, image.height, (context, width, height) => { + context.drawImage(image, 0, 0, width, height); + }) + .then(resolve) + .catch(reject); + }; + image.onerror = reject; + + image.src = imageUrl; + }); +}; + +export const captureVideoThumbnail = (video: HTMLVideoElement) => { + return capture(video.videoWidth, video.videoHeight, (context, width, height) => { + context.drawImage(video, 0, 0, width, height); + }); +}; + const generateVideoThumbnail = (videoUrl: string, time = 0) => { return new Promise((resolve, reject) => { const video = document.createElement("video"); - video.onloadeddata = () => { - video.currentTime = time; - }; - video.onseeked = () => { - captureVideoThumbnail(video).then(resolve).catch(reject); + video.onloadedmetadata = () => { + video.currentTime = Math.min(time, video.duration); + video.requestVideoFrameCallback(() => { + captureVideoThumbnail(video).then(resolve).catch(reject); + }); }; video.onerror = reject;