From 6bd3efd6f068c793933c9453ab56b5745354c91b Mon Sep 17 00:00:00 2001 From: not-nullptr Date: Thu, 14 Nov 2024 10:58:56 +0000 Subject: [PATCH] chore: refactor IFile to VertFile --- src/lib/converters/converter.svelte.ts | 6 +- src/lib/converters/ffmpeg.svelte.ts | 23 ++++---- src/lib/converters/magick.svelte.ts | 82 -------------------------- src/lib/converters/vips.svelte.ts | 9 +-- src/lib/store/index.svelte.ts | 13 +--- src/lib/types/conversion-worker.ts | 6 +- src/lib/types/file.svelte.ts | 25 ++++++++ src/lib/types/file.ts | 5 -- src/lib/types/index.ts | 2 +- src/lib/workers/magick.ts | 77 ------------------------ src/lib/workers/vips.ts | 19 +++--- src/routes/+page.svelte | 66 +++++++++++++++------ src/routes/convert/+page.svelte | 31 +++------- 13 files changed, 116 insertions(+), 248 deletions(-) delete mode 100644 src/lib/converters/magick.svelte.ts create mode 100644 src/lib/types/file.svelte.ts delete mode 100644 src/lib/types/file.ts delete mode 100644 src/lib/workers/magick.ts diff --git a/src/lib/converters/converter.svelte.ts b/src/lib/converters/converter.svelte.ts index 076b001..97296ce 100644 --- a/src/lib/converters/converter.svelte.ts +++ b/src/lib/converters/converter.svelte.ts @@ -1,4 +1,4 @@ -import type { IFile, OmitBetterStrict } from "$lib/types"; +import type { VertFile } from "$lib/types"; /** * Base class for all converters. @@ -21,10 +21,10 @@ export class Converter { public async convert( // eslint-disable-next-line @typescript-eslint/no-unused-vars - input: OmitBetterStrict, + input: VertFile, // eslint-disable-next-line @typescript-eslint/no-unused-vars to: string, - ): Promise { + ): Promise { throw new Error("Not implemented"); } } diff --git a/src/lib/converters/ffmpeg.svelte.ts b/src/lib/converters/ffmpeg.svelte.ts index da5bc50..c5bf456 100644 --- a/src/lib/converters/ffmpeg.svelte.ts +++ b/src/lib/converters/ffmpeg.svelte.ts @@ -1,6 +1,5 @@ -import type { IFile } from "$lib/types"; +import { VertFile } from "$lib/types"; import { Converter } from "./converter.svelte"; -import type { OmitBetterStrict } from "$lib/types"; import { FFmpeg } from "@ffmpeg/ffmpeg"; import { browser } from "$app/environment"; import { log } from "$lib/logger"; @@ -42,19 +41,23 @@ export class FFmpegConverter extends Converter { })(); } - public async convert( - input: OmitBetterStrict, - to: string, - ): Promise { + public async convert(input: VertFile, to: string): Promise { if (!to.startsWith(".")) to = `.${to}`; const ffmpeg = new FFmpeg(); + ffmpeg.on("progress", (progress) => { + log( + ["converters", this.name], + `progress for "${input.name}": ${progress.progress * 100}%`, + ); + input.progress = progress.progress * 100; + }); const baseURL = "https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm"; await ffmpeg.load({ coreURL: `${baseURL}/ffmpeg-core.js`, wasmURL: `${baseURL}/ffmpeg-core.wasm`, }); - const buf = new Uint8Array(input.buffer); + const buf = new Uint8Array(await input.file.arrayBuffer()); await ffmpeg.writeFile("input", buf); log( ["converters", this.name], @@ -70,10 +73,6 @@ export class FFmpegConverter extends Converter { `read ${input.name.split(".").slice(0, -1).join(".") + to} from ffmpeg virtual fs`, ); ffmpeg.terminate(); - return { - ...input, - buffer: output.buffer, - extension: to, - }; + return new VertFile(new File([output], input.name), to); } } diff --git a/src/lib/converters/magick.svelte.ts b/src/lib/converters/magick.svelte.ts deleted file mode 100644 index 9cfedd7..0000000 --- a/src/lib/converters/magick.svelte.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { IFile } from "$lib/types"; -import { Converter } from "./converter.svelte"; -import MagickWorker from "$lib/workers/magick?worker"; -import { browser } from "$app/environment"; -import type { WorkerMessage, OmitBetterStrict } from "$lib/types"; -import { MagickFormat } from "@imagemagick/magick-wasm"; - -const sortFirst = [".png", ".jpeg", ".jpg", ".webp", ".gif"]; - -export class MagickConverter extends Converter { - private worker: Worker = browser ? new MagickWorker() : null!; - private id = 0; - public name = "imagemagick"; - public ready = $state(false); - public supportedFormats = Object.keys(MagickFormat) - .map((key) => `.${key.toLowerCase()}`) - .sort((a, b) => { - const aIndex = sortFirst.indexOf(a); - const bIndex = sortFirst.indexOf(b); - if (aIndex === -1 && bIndex === -1) return a.localeCompare(b); - if (aIndex === -1) return 1; - if (bIndex === -1) return -1; - return aIndex - bIndex; - }); - - constructor() { - super(); - if (!browser) return; - this.worker.onmessage = (e) => { - const message: WorkerMessage = e.data; - if (message.type === "loaded") this.ready = true; - }; - } - - public async convert( - input: OmitBetterStrict, - to: string, - ): Promise { - const res = await this.sendMessage({ - type: "convert", - input: input as unknown as IFile, - to, - }); - - if (res.type === "finished") { - return res.output; - } - - if (res.type === "error") { - throw new Error(res.error); - } - - throw new Error("Unknown message type"); - } - - private sendMessage( - message: OmitBetterStrict, - ): Promise> { - const id = this.id++; - let resolved = false; - return new Promise((resolve) => { - const onMessage = (e: MessageEvent) => { - if (e.data.id === id) { - this.worker.removeEventListener("message", onMessage); - resolve(e.data); - resolved = true; - } - }; - - setTimeout(() => { - if (!resolved) { - this.worker.removeEventListener("message", onMessage); - throw new Error("Timeout"); - } - }, 60000); - - this.worker.addEventListener("message", onMessage); - - this.worker.postMessage({ ...message, id }); - }); - } -} diff --git a/src/lib/converters/vips.svelte.ts b/src/lib/converters/vips.svelte.ts index c109118..a6a6072 100644 --- a/src/lib/converters/vips.svelte.ts +++ b/src/lib/converters/vips.svelte.ts @@ -1,4 +1,4 @@ -import type { IFile } from "$lib/types"; +import { VertFile } from "$lib/types"; import { Converter } from "./converter.svelte"; import VipsWorker from "$lib/workers/vips?worker"; import { browser } from "$app/environment"; @@ -39,14 +39,11 @@ export class VipsConverter extends Converter { }; } - public async convert( - input: OmitBetterStrict, - to: string, - ): Promise { + public async convert(input: VertFile, to: string): Promise { log(["converters", this.name], `converting ${input.name} to ${to}`); const res = await this.sendMessage({ type: "convert", - input: input as unknown as IFile, + input, to, }); diff --git a/src/lib/store/index.svelte.ts b/src/lib/store/index.svelte.ts index 02023d7..d1f7068 100644 --- a/src/lib/store/index.svelte.ts +++ b/src/lib/store/index.svelte.ts @@ -1,17 +1,8 @@ import { log } from "$lib/logger"; -import type { IFile } from "$lib/types"; +import { VertFile } from "$lib/types"; class Files { - public files = $state< - { - file: File; - from: string; - to: string; - blobUrl: string; - id: string; - result?: (IFile & { blobUrl: string; animating: boolean }) | null; - }[] - >([]); + public files = $state([]); } class Theme { diff --git a/src/lib/types/conversion-worker.ts b/src/lib/types/conversion-worker.ts index 1a73b7a..c1b5bdc 100644 --- a/src/lib/types/conversion-worker.ts +++ b/src/lib/types/conversion-worker.ts @@ -1,14 +1,14 @@ -import type { IFile } from "./file"; +import { VertFile } from "./file.svelte"; interface ConvertMessage { type: "convert"; - input: IFile; + input: VertFile; to: string; } interface FinishedMessage { type: "finished"; - output: IFile; + output: VertFile; } interface LoadedMessage { diff --git a/src/lib/types/file.svelte.ts b/src/lib/types/file.svelte.ts new file mode 100644 index 0000000..157fa56 --- /dev/null +++ b/src/lib/types/file.svelte.ts @@ -0,0 +1,25 @@ +export class VertFile { + public id: string = Math.random().toString(36).slice(2, 8); + + public get from() { + return "." + this.file.name.split(".").pop()!; + } + + public get name() { + return this.file.name; + } + + public progress = $state(0); + // public result: VertFile | null = null; + public result = $state(null); + + public to = $state(""); + + constructor( + public readonly file: File, + to: string, + public readonly blobUrl?: string, + ) { + this.to = to; + } +} diff --git a/src/lib/types/file.ts b/src/lib/types/file.ts deleted file mode 100644 index fe42fc0..0000000 --- a/src/lib/types/file.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IFile { - name: string; - extension: string; - buffer: ArrayBuffer; -} diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index f1c0a1e..a69c1a0 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -1,3 +1,3 @@ -export * from "./file"; +export * from "./file.svelte"; export * from "./util"; export * from "./conversion-worker"; diff --git a/src/lib/workers/magick.ts b/src/lib/workers/magick.ts deleted file mode 100644 index 2d568e0..0000000 --- a/src/lib/workers/magick.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { WorkerMessage, OmitBetterStrict } from "$lib/types"; -import { - ImageMagick, - initializeImageMagick, - MagickFormat, -} from "@imagemagick/magick-wasm"; -import wasmUrl from "@imagemagick/magick-wasm/magick.wasm?url"; - -const magickPromise = fetch(wasmUrl) - .then((r) => r.arrayBuffer()) - .then((r) => initializeImageMagick(r)); - -magickPromise - .then(() => { - postMessage({ type: "loaded" }); - }) - .catch((error) => { - postMessage({ type: "error", error }); - }); - -const handleMessage = async ( - message: WorkerMessage, -): Promise | undefined> => { - await magickPromise; - switch (message.type) { - case "convert": { - if (!message.to.startsWith(".")) message.to = `.${message.to}`; - message.to = message.to.slice(1); - - // unfortunately this lib uses some hacks to dispose images when the promise is resolved - // this means we can't promisify it :( - return new Promise((resolve) => { - ImageMagick.read( - new Uint8Array(message.input.buffer), - (img) => { - const keys = Object.keys(MagickFormat); - const values = Object.values(MagickFormat); - const index = keys.findIndex( - (key) => - key.toLowerCase() === message.to.toLowerCase(), - ); - const format = values[index]; - img.write(format, (output) => { - resolve({ - type: "finished", - output: { - ...message.input, - buffer: output, - extension: message.to, - }, - }); - }); - img.dispose(); - }, - ); - }); - } - } -}; - -onmessage = async (e) => { - const message: WorkerMessage = e.data; - try { - const res = await handleMessage(message); - if (!res) return; - postMessage({ - ...res, - id: message.id, - }); - } catch (e) { - postMessage({ - type: "error", - error: e, - id: message.id, - }); - } -}; diff --git a/src/lib/workers/vips.ts b/src/lib/workers/vips.ts index fbf5dbc..30fcbee 100644 --- a/src/lib/workers/vips.ts +++ b/src/lib/workers/vips.ts @@ -1,4 +1,8 @@ -import type { WorkerMessage, OmitBetterStrict } from "$lib/types"; +import { + type WorkerMessage, + type OmitBetterStrict, + VertFile, +} from "$lib/types"; import Vips from "wasm-vips"; const vipsPromise = Vips({ @@ -21,16 +25,17 @@ const handleMessage = async ( switch (message.type) { case "convert": { if (!message.to.startsWith(".")) message.to = `.${message.to}`; - const image = vips.Image.newFromBuffer(message.input.buffer); + const image = vips.Image.newFromBuffer( + await message.input.file.arrayBuffer(), + ); const output = image.writeToBuffer(message.to); image.delete(); return { type: "finished", - output: { - ...message.input, - buffer: output.buffer, - extension: message.to, - }, + output: new VertFile( + new File([output.buffer], message.input.name), + message.to, + ), }; } } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 78f7607..df69afb 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,6 +4,7 @@ import { converters } from "$lib/converters"; import { log } from "$lib/logger/index.js"; import { files } from "$lib/store/index.svelte"; + import { VertFile } from "$lib/types/file.svelte.js"; import { Check } from "lucide-svelte"; const { data } = $props(); @@ -42,31 +43,58 @@ ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); // get the blob canvas.toBlob( - (blob) => { - resolve({ - file: f, - from, - to, - blobUrl: - blob === null - ? "" - : URL.createObjectURL(blob), - id: Math.random().toString(36).substring(2), - }); + async (blob) => { + // resolve({ + // file: f, + // from, + // to, + // blobUrl: + // blob === null + // ? "" + // : URL.createObjectURL(blob), + // id: Math.random().toString(36).substring(2), + // buffer: await f.arrayBuffer(), + // extension: from, + // name: f.name, + // result: null, + // progress: 0, + // }); + resolve( + new VertFile( + new File([blob!], f.name, { + type: blob!.type, + }), + to, + URL.createObjectURL(blob!), + ), + ); }, "image/jpeg", 0.75, ); }; - img.onerror = () => { - resolve({ - file: f, - from, - to, - blobUrl: "", - id: Math.random().toString(36).substring(2), - }); + img.onerror = async () => { + // resolve({ + // file: f, + // from, + // to, + // blobUrl: "", + // id: Math.random().toString(36).substring(2), + // name: f.name, + // buffer: await f.arrayBuffer(), + // extension: from, + // result: null, + // progress: 0, + // }); + resolve( + new VertFile( + new File([await f.arrayBuffer()], f.name, { + type: f.type, + }), + to, + ), + ); }; }, ); diff --git a/src/routes/convert/+page.svelte b/src/routes/convert/+page.svelte index 4d389a2..bc29550 100644 --- a/src/routes/convert/+page.svelte +++ b/src/routes/convert/+page.svelte @@ -81,25 +81,8 @@ if (!converter) throw new Error("No converter found"); const to = file.to; processings[i] = true; - const converted = await converter.convert( - { - name: file.file.name, - buffer: await file.file.arrayBuffer(), - }, - to, - ); - files.files[i] = { - ...file, - result: { - ...converted, - blobUrl: URL.createObjectURL( - new Blob([converted.buffer], { - type: file.file.type, - }), - ), - animating: true, - }, - }; + const converted = await converter.convert(file, to); + file.result = converted; processings[i] = false; })(), ); @@ -123,7 +106,7 @@ dlFiles.push({ name: file.file.name.replace(/\.[^/.]+$/, "") + file.to, lastModified: Date.now(), - input: result.buffer, + input: await result.file.arrayBuffer(), }); } if (files.files.length === 0) return; @@ -257,7 +240,7 @@ ); })()}
+