From 7fb1daf2cd0ceb65d1a29f86634af1e6e5c6b54c Mon Sep 17 00:00:00 2001 From: not-nullptr Date: Mon, 11 Nov 2024 15:26:24 +0000 Subject: [PATCH] feat: file converting --- src/lib/converters/converter.ts | 10 ++++-- src/lib/converters/index.svelte.ts | 15 -------- src/lib/converters/index.ts | 3 ++ src/lib/converters/vips.ts | 58 ++++++++++++++++++++++++++++-- src/lib/types/index.ts | 2 ++ src/lib/types/util.ts | 3 ++ src/lib/types/vips-worker.ts | 16 +++++++++ src/lib/workers/vips.ts | 40 +++++++++++++++++++-- src/routes/+layout.ts | 10 ------ src/routes/+page.svelte | 24 +++++++++---- 10 files changed, 141 insertions(+), 40 deletions(-) delete mode 100644 src/lib/converters/index.svelte.ts create mode 100644 src/lib/converters/index.ts create mode 100644 src/lib/types/util.ts create mode 100644 src/lib/types/vips-worker.ts delete mode 100644 src/routes/+layout.ts diff --git a/src/lib/converters/converter.ts b/src/lib/converters/converter.ts index 237db11..bb371f4 100644 --- a/src/lib/converters/converter.ts +++ b/src/lib/converters/converter.ts @@ -1,9 +1,13 @@ -import type { IFile } from "$lib/types"; +import type { IFile, OmitBetterStrict } from "$lib/types"; export class Converter { public supportedFormats: string[] = []; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async convert(input: IFile, output: IFile): Promise { + public async convert( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + input: OmitBetterStrict, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + to: string, + ): Promise { throw new Error("Not implemented"); } } diff --git a/src/lib/converters/index.svelte.ts b/src/lib/converters/index.svelte.ts deleted file mode 100644 index f593609..0000000 --- a/src/lib/converters/index.svelte.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Vips from "wasm-vips"; - -class Converters { - public vips = $state(null!); - public loaded = $derived(this.vips !== null); -} - -export const converters = new Converters(); - -// Vips().then((vips) => { -// converters.vips = vips; -// }); - -// the above *does* work but it blocks the ui thread whilst wasm is loading -// we can use a web worker to remedy this, see +layout.ts for details diff --git a/src/lib/converters/index.ts b/src/lib/converters/index.ts new file mode 100644 index 0000000..0f009af --- /dev/null +++ b/src/lib/converters/index.ts @@ -0,0 +1,3 @@ +import { VipsConverter } from "./vips"; + +export const converters = [new VipsConverter()]; diff --git a/src/lib/converters/vips.ts b/src/lib/converters/vips.ts index 1a46ee2..9102a13 100644 --- a/src/lib/converters/vips.ts +++ b/src/lib/converters/vips.ts @@ -1,9 +1,63 @@ import type { IFile } from "$lib/types"; import { Converter } from "./converter"; +import VipsWorker from "$lib/workers/vips?worker"; +import { browser } from "$app/environment"; +import type { VipsWorkerMessage, OmitBetterStrict } from "$lib/types"; export class VipsConverter extends Converter { + private worker: Worker = browser ? new VipsWorker() : null!; + private id = 0; public supportedFormats = [""]; - public convert(input: IFile, output: IFile): Promise { - throw new Error("Not implemented"); + + constructor() { + super(); + if (!browser) return; + this.worker.onmessage = (e) => { + console.log(e.data); + }; + } + + 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; + } + + 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"); + } + }, 20000); + + this.worker.addEventListener("message", onMessage); + + this.worker.postMessage({ ...message, id }); + }); } } diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 375123f..947c3eb 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -1 +1,3 @@ export * from "./file"; +export * from "./util"; +export * from "./vips-worker"; diff --git a/src/lib/types/util.ts b/src/lib/types/util.ts new file mode 100644 index 0000000..5e64220 --- /dev/null +++ b/src/lib/types/util.ts @@ -0,0 +1,3 @@ +export type OmitBetterStrict = T extends unknown + ? Pick> + : never; diff --git a/src/lib/types/vips-worker.ts b/src/lib/types/vips-worker.ts new file mode 100644 index 0000000..8358a42 --- /dev/null +++ b/src/lib/types/vips-worker.ts @@ -0,0 +1,16 @@ +import type { IFile } from "./file"; + +interface VipsConvertMessage { + type: "convert"; + input: IFile; + to: string; +} + +interface VipsFinishedMessage { + type: "finished"; + output: IFile; +} + +export type VipsWorkerMessage = (VipsConvertMessage | VipsFinishedMessage) & { + id: number; +}; diff --git a/src/lib/workers/vips.ts b/src/lib/workers/vips.ts index 8e76e12..b8440a5 100644 --- a/src/lib/workers/vips.ts +++ b/src/lib/workers/vips.ts @@ -1,9 +1,43 @@ +import type { VipsWorkerMessage, OmitBetterStrict } from "$lib/types"; import Vips from "wasm-vips"; -Vips() - .then((vips) => { - postMessage({ type: "success", vips }); +const vipsPromise = Vips(); + +vipsPromise + .then(() => { + postMessage({ type: "loaded" }); }) .catch((error) => { postMessage({ type: "error", error }); }); + +const handleMessage = async ( + message: VipsWorkerMessage, +): Promise | undefined> => { + const vips = await vipsPromise; + switch (message.type) { + case "convert": { + if (!message.to.startsWith(".")) message.to = `.${message.to}`; + const image = vips.Image.newFromBuffer(message.input.buffer); + const output = image.writeToBuffer(message.to); + return { + type: "finished", + output: { + ...message.input, + buffer: output.buffer, + extension: message.to, + }, + }; + } + } +}; + +onmessage = async (e) => { + const message: VipsWorkerMessage = e.data; + const res = await handleMessage(message); + if (!res) return; + postMessage({ + ...res, + id: message.id, + }); +}; diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts deleted file mode 100644 index b10c57d..0000000 --- a/src/routes/+layout.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { browser } from "$app/environment"; -import VipsWorker from "$lib/workers/vips?worker"; - -export const load = () => { - if (!browser) return; - const worker = new VipsWorker(); - worker.onmessage = (e) => { - console.log(e.data); - }; -}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8c5fa8c..418beea 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,12 +1,22 @@ - +
+ file = (e.target as any).files[0]} /> + + +
\ No newline at end of file