diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index e2652a1..2c84f93 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -11,7 +11,7 @@ import { digestMessage, signMessageHmac, } from "$lib/modules/crypto"; -import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; +import { generateThumbnail } from "$lib/modules/thumbnail"; import type { DuplicateFileScanRequest, DuplicateFileScanResponse, @@ -78,30 +78,6 @@ const extractExifDateTime = (fileBuffer: ArrayBuffer) => { return new Date(utcDate - offsetMs); }; -const generateThumbnail = async (file: File, fileType: string) => { - let url; - try { - if (fileType === "image/heic") { - const { default: heic2any } = await import("heic2any"); - url = URL.createObjectURL((await heic2any({ blob: file, toType: "image/png" })) as Blob); - return await generateImageThumbnail(url); - } else if (fileType.startsWith("image/")) { - url = URL.createObjectURL(file); - return await generateImageThumbnail(url); - } else if (fileType.startsWith("video/")) { - url = URL.createObjectURL(file); - return await generateVideoThumbnail(url); - } - return null; - } catch { - return null; - } finally { - if (url) { - URL.revokeObjectURL(url); - } - } -}; - const encryptFile = limitFunction( async ( status: Writable, @@ -132,7 +108,7 @@ const encryptFile = limitFunction( createdAt && (await encryptString(createdAt.getTime().toString(), dataKey)); const lastModifiedAtEncrypted = await encryptString(file.lastModified.toString(), dataKey); - const thumbnail = await generateThumbnail(file, fileType); + const thumbnail = await generateThumbnail(fileBuffer, fileType); const thumbnailBuffer = await thumbnail?.arrayBuffer(); const thumbnailEncrypted = thumbnailBuffer ? await encryptData(thumbnailBuffer, dataKey) : null; diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts index 2352c65..1a24b5d 100644 --- a/src/lib/modules/thumbnail.ts +++ b/src/lib/modules/thumbnail.ts @@ -12,7 +12,7 @@ const scaleSize = (width: number, height: number, targetSize: number) => { }; }; -export const generateImageThumbnail = (imageUrl: string) => { +const generateImageThumbnail = (imageUrl: string) => { return new Promise((resolve, reject) => { const image = new Image(); image.onload = () => { @@ -42,7 +42,7 @@ export const generateImageThumbnail = (imageUrl: string) => { }); }; -export const generateVideoThumbnail = (videoUrl: string, time = 0) => { +const generateVideoThumbnail = (videoUrl: string, time = 0) => { return new Promise((resolve, reject) => { const video = document.createElement("video"); video.onloadeddata = () => { @@ -77,6 +77,35 @@ export const generateVideoThumbnail = (videoUrl: string, time = 0) => { }); }; +export const generateThumbnail = async (fileBuffer: ArrayBuffer, fileType: string) => { + let url; + try { + if (fileType === "image/heic") { + const { default: heic2any } = await import("heic2any"); + url = URL.createObjectURL( + (await heic2any({ + blob: new Blob([fileBuffer], { type: fileType }), + toType: "image/png", + })) as Blob, + ); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("image/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateImageThumbnail(url); + } else if (fileType.startsWith("video/")) { + url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); + return await generateVideoThumbnail(url); + } + return null; + } catch { + return null; + } finally { + if (url) { + URL.revokeObjectURL(url); + } + } +}; + export const getThumbnailUrl = (thumbnailBuffer: ArrayBuffer) => { return `data:image/webp;base64,${encodeToBase64(thumbnailBuffer)}`; }; diff --git a/src/lib/server/db/media.ts b/src/lib/server/db/media.ts index 8386ffc..209e256 100644 --- a/src/lib/server/db/media.ts +++ b/src/lib/server/db/media.ts @@ -106,5 +106,5 @@ export const getMissingFileThumbnails = async (userId: number, limit: number = 1 ) .limit(limit) .execute(); - return files.map((file) => file.id); + return files.map(({ id }) => id); }; diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 1a95538..9ee6e1d 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -36,8 +36,8 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp if (!res.ok) return null; const thumbnailEncrypted = await res.arrayBuffer(); - const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); + const thumbnailBuffer = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); - storeFileThumbnailCache(fileId, thumbnail); // Intended - return getThumbnailUrl(thumbnail); + storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended + return getThumbnailUrl(thumbnailBuffer); }; diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts index 4e430d5..aaee616 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -3,7 +3,7 @@ import { get, writable, type Writable } from "svelte/store"; import { encryptData } from "$lib/modules/crypto"; import { storeFileThumbnailCache } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; -import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; +import { generateThumbnail as doGenerateThumbnail } from "$lib/modules/thumbnail"; import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; import { requestFileDownload } from "$lib/services/file"; @@ -38,42 +38,18 @@ const generateThumbnail = limitFunction( fileType: string, dataKey: CryptoKey, ) => { - let url, thumbnail; status.set("generating"); - - try { - if (fileType === "image/heic") { - const { default: heic2any } = await import("heic2any"); - url = URL.createObjectURL( - (await heic2any({ - blob: new Blob([fileBuffer], { type: fileType }), - toType: "image/png", - })) as Blob, - ); - thumbnail = await generateImageThumbnail(url); - } else if (fileType.startsWith("image/")) { - url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); - thumbnail = await generateImageThumbnail(url); - } else if (fileType.startsWith("video/")) { - url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); - thumbnail = await generateVideoThumbnail(url); - } else { - status.set("error"); - return null; - } - - const thumbnailBuffer = await thumbnail.arrayBuffer(); - const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); - status.set("upload-pending"); - return { plaintext: thumbnailBuffer, ...thumbnailEncrypted }; - } catch { + const thumbnail = await doGenerateThumbnail(fileBuffer, fileType); + if (!thumbnail) { status.set("error"); return null; - } finally { - if (url) { - URL.revokeObjectURL(url); - } } + + const thumbnailBuffer = await thumbnail.arrayBuffer(); + const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey); + + status.set("upload-pending"); + return { plaintext: thumbnailBuffer, ...thumbnailEncrypted }; }, { concurrency: 4 }, );