mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
비디오의 경우 원하는 장면으로 썸네일을 변경할 수 있도록 개선
This commit is contained in:
@@ -42,6 +42,30 @@ const generateImageThumbnail = (imageUrl: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const captureVideoThumbnail = (video: HTMLVideoElement) => {
|
||||
return new Promise<Blob>((resolve, reject) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const { width, height } = scaleSize(video.videoWidth, video.videoHeight, 250);
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
return reject(new Error("Failed to generate thumbnail"));
|
||||
}
|
||||
|
||||
context.drawImage(video, 0, 0, width, height);
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
resolve(blob);
|
||||
} else {
|
||||
reject(new Error("Failed to generate thumbnail"));
|
||||
}
|
||||
}, "image/webp");
|
||||
});
|
||||
};
|
||||
|
||||
const generateVideoThumbnail = (videoUrl: string, time = 0) => {
|
||||
return new Promise<Blob>((resolve, reject) => {
|
||||
const video = document.createElement("video");
|
||||
@@ -49,25 +73,7 @@ const generateVideoThumbnail = (videoUrl: string, time = 0) => {
|
||||
video.currentTime = time;
|
||||
};
|
||||
video.onseeked = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const { width, height } = scaleSize(video.videoWidth, video.videoHeight, 250);
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
return reject(new Error("Failed to generate thumbnail"));
|
||||
}
|
||||
|
||||
context.drawImage(video, 0, 0, width, height);
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
resolve(blob);
|
||||
} else {
|
||||
reject(new Error("Failed to generate thumbnail"));
|
||||
}
|
||||
}, "image/webp");
|
||||
captureVideoThumbnail(video).then(resolve).catch(reject);
|
||||
};
|
||||
video.onerror = reject;
|
||||
|
||||
|
||||
@@ -11,15 +11,18 @@
|
||||
type FileInfo,
|
||||
type CategoryInfo,
|
||||
} from "$lib/modules/filesystem";
|
||||
import { captureVideoThumbnail } from "$lib/modules/thumbnail";
|
||||
import { fileDownloadStatusStore, isFileDownloading, masterKeyStore } from "$lib/stores";
|
||||
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
|
||||
import DownloadStatus from "./DownloadStatus.svelte";
|
||||
import {
|
||||
requestFileRemovalFromCategory,
|
||||
requestFileDownload,
|
||||
requestThumbnailUpload,
|
||||
requestFileAdditionToCategory,
|
||||
} from "./service";
|
||||
|
||||
import IconCamera from "~icons/material-symbols/camera";
|
||||
import IconClose from "~icons/material-symbols/close";
|
||||
import IconAddCircle from "~icons/material-symbols/add-circle";
|
||||
|
||||
@@ -40,6 +43,7 @@
|
||||
let isDownloadRequested = $state(false);
|
||||
let viewerType: "image" | "video" | undefined = $state();
|
||||
let fileBlobUrl: string | undefined = $state();
|
||||
let videoElement: HTMLVideoElement | undefined = $state();
|
||||
|
||||
const updateViewer = async (buffer: ArrayBuffer, contentType: string) => {
|
||||
const fileBlob = new Blob([buffer], { type: contentType });
|
||||
@@ -55,6 +59,11 @@
|
||||
return fileBlob;
|
||||
};
|
||||
|
||||
const updateThumbnail = async (dataKey: CryptoKey, dataKeyVersion: Date) => {
|
||||
const thumbnail = await captureVideoThumbnail(videoElement!);
|
||||
await requestThumbnailUpload(data.id, thumbnail, dataKey, dataKeyVersion);
|
||||
};
|
||||
|
||||
const addToCategory = async (categoryId: number) => {
|
||||
await requestFileAdditionToCategory(data.id, categoryId);
|
||||
isAddToCategoryBottomSheetOpen = false;
|
||||
@@ -133,8 +142,17 @@
|
||||
{/if}
|
||||
{:else if viewerType === "video"}
|
||||
{#if fileBlobUrl}
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video src={fileBlobUrl} controls></video>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video bind:this={videoElement} src={fileBlobUrl} controls muted></video>
|
||||
<IconEntryButton
|
||||
icon={IconCamera}
|
||||
onclick={() => updateThumbnail($info.dataKey!, $info.dataKeyVersion!)}
|
||||
class="w-full"
|
||||
>
|
||||
이 장면을 썸네일로 설정하기
|
||||
</IconEntryButton>
|
||||
</div>
|
||||
{:else}
|
||||
{@render viewerLoading("비디오를 불러오고 있어요.")}
|
||||
{/if}
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
import { callPostApi } from "$lib/hooks";
|
||||
import type { CategoryFileAddRequest } from "$lib/server/schemas";
|
||||
import { encryptData } from "$lib/modules/crypto";
|
||||
import { storeFileThumbnailCache } from "$lib/modules/file";
|
||||
import type { CategoryFileAddRequest, FileThumbnailUploadRequest } from "$lib/server/schemas";
|
||||
|
||||
export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category";
|
||||
export { requestFileDownload } from "$lib/services/file";
|
||||
|
||||
export const requestThumbnailUpload = async (
|
||||
fileId: number,
|
||||
thumbnail: Blob,
|
||||
dataKey: CryptoKey,
|
||||
dataKeyVersion: Date,
|
||||
) => {
|
||||
const thumbnailBuffer = await thumbnail.arrayBuffer();
|
||||
const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey);
|
||||
|
||||
const form = new FormData();
|
||||
form.set(
|
||||
"metadata",
|
||||
JSON.stringify({
|
||||
dekVersion: dataKeyVersion.toISOString(),
|
||||
contentIv: thumbnailEncrypted.iv,
|
||||
} satisfies FileThumbnailUploadRequest),
|
||||
);
|
||||
form.set("content", new Blob([thumbnailEncrypted.ciphertext]));
|
||||
|
||||
const res = await fetch(`/api/file/${fileId}/thumbnail/upload`, { method: "POST", body: form });
|
||||
if (!res.ok) return false;
|
||||
|
||||
storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended
|
||||
return true;
|
||||
};
|
||||
|
||||
export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => {
|
||||
const res = await callPostApi<CategoryFileAddRequest>(`/api/category/${categoryId}/file/add`, {
|
||||
file: fileId,
|
||||
|
||||
Reference in New Issue
Block a user