({});
const handleSettingChange = (key: string, value: any) => {
if (!file) return;
settings[key] = value;
- console.log(
- `Changed settings for ${file.name}: ${JSON.stringify(settings, null, 2)}`,
- );
};
- const applySettings = () => {
+ const applySettings = async () => {
onclose?.();
if (!file) return;
- file.conversionSettings = { ...file.conversionSettings, ...settings };
- console.log(
+ const converter = file.findConverter();
+ if (!converter) {
+ log(
+ ["settings", "modal"],
+ `No converter found for ${file.name}, cannot apply settings`,
+ );
+ return;
+ }
+ // apply defaults, then existing settings, then new settings on top
+ file.conversionSettings = {
+ ...(await converter.getDefaultSettings()),
+ ...file.conversionSettings,
+ ...settings,
+ };
+ log(
+ ["settings", "modal"],
`Applied settings for ${file.name}: ${JSON.stringify(file.conversionSettings, null, 2)}`,
);
};
@@ -58,7 +71,8 @@
{@html sanitize(
m["convert.settings.description"]({
- converter: file.findConverter()?.name,
+ converter:
+ file.findConverter()?.name || "unknown",
filename: file.name,
}),
)}
diff --git a/src/lib/components/visual/Toast.svelte b/src/lib/components/visual/Toast.svelte
index b934e55..bd36250 100644
--- a/src/lib/components/visual/Toast.svelte
+++ b/src/lib/components/visual/Toast.svelte
@@ -12,12 +12,18 @@
import type { ToastProps } from "$lib/util/toast.svelte";
import type { SvelteComponent } from "svelte";
import clsx from "clsx";
+ import type { Toast as ToastType } from "$lib/util/toast.svelte";
- const { id, type, message, durations, ...rest }: ToastProps = $props();
+ const props: {
+ toast: ToastType;
+ } = $props();
- const additional = $derived(
- "additional" in rest ? rest.additional : undefined,
- );
+ // svelte-ignore state_referenced_locally
+ const { id, type, message, durations } = props.toast;
+
+ // svelte-ignore state_referenced_locally
+ const additional =
+ "additional" in props.toast ? props.toast.additional : {};
const colors = {
success: "purple",
diff --git a/src/lib/converters/converter.svelte.ts b/src/lib/converters/converter.svelte.ts
index 8c0448d..e59ef26 100644
--- a/src/lib/converters/converter.svelte.ts
+++ b/src/lib/converters/converter.svelte.ts
@@ -93,7 +93,7 @@ export class Converter {
public async convert(
input: VertFile,
to: string,
- settings?: ConversionSettings,
+ settings: ConversionSettings,
...args: any[]
): Promise {
throw new Error("Not implemented");
diff --git a/src/lib/converters/magick.svelte.ts b/src/lib/converters/magick.svelte.ts
index 3a51433..ec676c9 100644
--- a/src/lib/converters/magick.svelte.ts
+++ b/src/lib/converters/magick.svelte.ts
@@ -118,12 +118,13 @@ export class MagickConverter extends Converter {
public async getAvailableSettings(): Promise {
// images - quality/compression/quantize/interlace/depth-DPI, resize, crop, rotate, flip/flop, autoOrient?, color space/bit depth, transparency settings
-
+ const global = Settings.instance.settings;
+
const quality: SettingDefinition = {
key: "quality",
label: m["convert.settings.image.quality"](),
type: "number",
- default: 100,
+ default: global.magickQuality ?? 100,
min: 0,
max: 100,
};
@@ -152,6 +153,7 @@ export class MagickConverter extends Converter {
// what are these even lmao
{ value: "auto", label: "Auto" },
{ value: "srgb", label: "sRGB" },
+ { value: "cmyk", label: "CMYK" },
{ value: "adobe98", label: "Adobe RGB" },
{ value: "prophoto", label: "ProPhoto RGB" },
{ value: "displayp3", label: "Display P3" },
@@ -174,7 +176,7 @@ export class MagickConverter extends Converter {
key: "metadata",
label: m["convert.settings.common.metadata"](),
type: "boolean",
- default: true,
+ default: global.metadata ?? true,
};
// resize, crop, rotate - prob want a ui
@@ -194,17 +196,10 @@ export class MagickConverter extends Converter {
public async convert(
input: VertFile,
to: string,
+ settings: ConversionSettings,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: any[]
): Promise {
- let compression: number | undefined = args.at(0);
- if (!compression) {
- compression = Settings.instance.settings.magickQuality ?? 100;
- log(
- ["converters", this.name],
- `using user setting for quality: ${compression}%`,
- );
- }
log(["converters", this.name], `converting ${input.name} to ${to}`);
// handle converting from SVG manually because magick-wasm doesn't support it
@@ -216,7 +211,7 @@ export class MagickConverter extends Converter {
input.to,
);
if (to === ".png") return pngFile; // if target is png, return it directly
- return await this.convert(pngFile, to, ...args); // otherwise, recursively convert png to user's target format
+ return await this.convert(pngFile, to, settings, ...args); // otherwise, recursively convert png to user's target format
} catch (err) {
error(
["converters", this.name],
@@ -270,9 +265,11 @@ export class MagickConverter extends Converter {
]);
// every other format handled by magick worker
- const keepMetadata: boolean =
- Settings.instance.settings.metadata ?? true;
- log(["converters", this.name], `keep metadata: ${keepMetadata}`);
+ const conversionSettings = JSON.stringify(
+ Object.keys(settings).length > 0
+ ? settings // user-provided settings
+ : await this.getDefaultSettings(), // use defaults if not provided
+ );
const convertMsg: WorkerMessage = {
type: "convert",
id: input.id,
@@ -283,8 +280,7 @@ export class MagickConverter extends Converter {
to: input.to,
},
to,
- compression,
- keepMetadata,
+ conversionSettings,
};
worker.postMessage(convertMsg);
diff --git a/src/lib/converters/vertd.svelte.ts b/src/lib/converters/vertd.svelte.ts
index 66969b2..93279d3 100644
--- a/src/lib/converters/vertd.svelte.ts
+++ b/src/lib/converters/vertd.svelte.ts
@@ -422,7 +422,7 @@ export class VertdConverter extends Converter {
return defaults;
}
- public async convert(input: VertFile, to: string): Promise {
+ public async convert(input: VertFile, to: string, settings: ConversionSettings): Promise {
if (to.startsWith(".")) to = to.slice(1);
let fileUpload = input;
@@ -440,7 +440,7 @@ export class VertdConverter extends Converter {
fileUpload = await magickConverter.convert(
input,
".gif",
- input.conversionSettings,
+ settings,
100,
);
this.log(`successfully converted webp to gif`);
diff --git a/src/lib/types/conversion-settings.ts b/src/lib/types/conversion-settings.ts
index 0a8fc7a..bb6e640 100644
--- a/src/lib/types/conversion-settings.ts
+++ b/src/lib/types/conversion-settings.ts
@@ -10,7 +10,7 @@ export interface SettingDefinition {
min?: number;
max?: number;
step?: number;
- options?: Array<{ value: string; label: string }>; // for select types
+ options?: Array<{ value: any; label: string }>; // for select types
description?: string;
}
diff --git a/src/lib/types/conversion-worker.ts b/src/lib/types/conversion-worker.ts
index 3576960..504c068 100644
--- a/src/lib/types/conversion-worker.ts
+++ b/src/lib/types/conversion-worker.ts
@@ -9,8 +9,7 @@ interface ConvertMessage {
to: string;
} | VertFile;
to: string;
- compression: number | null;
- keepMetadata?: boolean;
+ conversionSettings: string; // JSON stringified ConversionSettings
}
interface FinishedMessage {
diff --git a/src/lib/types/file.svelte.ts b/src/lib/types/file.svelte.ts
index 5b25bd8..82d5b81 100644
--- a/src/lib/types/file.svelte.ts
+++ b/src/lib/types/file.svelte.ts
@@ -188,6 +188,7 @@ export class VertFile {
const converted = await converter.convert(
tempVFile,
this.to,
+ this.conversionSettings,
);
let outputExt = this.to;
@@ -209,6 +210,7 @@ export class VertFile {
const converted = await converter.convert(
tempVFile,
this.to,
+ this.conversionSettings,
);
let outputExt = this.to;
diff --git a/src/lib/workers/magick.ts b/src/lib/workers/magick.ts
index aa3ab5d..4eb547b 100644
--- a/src/lib/workers/magick.ts
+++ b/src/lib/workers/magick.ts
@@ -1,15 +1,20 @@
import {
+ ColorSpace,
initializeImageMagick,
+ MagickColor,
MagickFormat,
MagickImage,
MagickImageCollection,
MagickReadSettings,
+ AlphaAction,
type IMagickImage,
} from "@imagemagick/magick-wasm";
import { makeZip } from "client-zip";
import { parseAni } from "$lib/util/parse/ani";
import { parseIcns } from "vert-wasm";
import type { WorkerMessage } from "$lib/types";
+import type { ConversionSettings } from "$lib/types/conversion-settings";
+import { log } from "$lib/util/logger";
let magickInitialized = false;
@@ -44,9 +49,6 @@ const handleMessage = async (
return { type: "error", error: "magick-wasm not initialized" };
}
- const compression: number | undefined =
- message.compression ?? undefined;
- const keepMetadata: boolean = message.keepMetadata ?? true;
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
message.to = message.to.toLowerCase();
if (message.to === ".jfif") message.to = ".jpeg";
@@ -55,6 +57,10 @@ const handleMessage = async (
if (from === ".jfif") from = ".jpeg";
if (from === ".fit") from = ".fits";
+ console.log(JSON.stringify(message, null, 2));
+ const conversionSettings = JSON.parse(
+ message.conversionSettings || "{}",
+ ) as ConversionSettings;
const buffer = await message.input.file.arrayBuffer();
// special ico handling to split them all into separate images
@@ -90,8 +96,7 @@ const handleMessage = async (
const output = await magickConvert(
img,
message.to,
- keepMetadata,
- compression,
+ conversionSettings,
);
convertedImgs[i] = output;
}),
@@ -133,8 +138,7 @@ const handleMessage = async (
}),
),
message.to,
- keepMetadata,
- compression,
+ conversionSettings,
);
files.push(
new File(
@@ -184,8 +188,7 @@ const handleMessage = async (
const converted = await magickConvert(
img,
message.to,
- keepMetadata,
- compression,
+ conversionSettings,
);
outputs.push(converted);
break;
@@ -251,8 +254,7 @@ const handleMessage = async (
const converted = await magickConvert(
img,
message.to,
- keepMetadata,
- compression,
+ conversionSettings,
);
return {
@@ -287,8 +289,7 @@ const readToEnd = async (reader: ReadableStreamDefaultReader) => {
const magickConvert = async (
img: IMagickImage,
to: string,
- keepMetadata: boolean,
- compression?: number,
+ conversionSettings: ConversionSettings,
) => {
let fmt = to.slice(1).toUpperCase();
if (fmt === "JFIF") fmt = "JPEG";
@@ -310,10 +311,56 @@ const magickConvert = async (
const result = await new Promise((resolve, reject) => {
try {
- // magick-wasm automatically clamps (https://github.com/dlemstra/magick-wasm/blob/76fc6f2b0c0497d2ddc251bbf6174b4dc92ac3ea/src/magick-image.ts#L2480)
- if (compression) img.quality = compression;
- if (!keepMetadata) img.strip();
+ // quality, depth, colorSpace, transparency, metadata
+ const quality = conversionSettings.quality as number;
+ const bitDepth = conversionSettings.depth as number;
+ const colorSpace = conversionSettings.colorSpace as string;
+ const transparency = conversionSettings.transparency as boolean;
+ const metadata = conversionSettings.metadata as boolean;
+ if (quality) img.quality = quality;
+ if (bitDepth) img.depth = bitDepth;
+ if (!metadata) img.strip();
+ if (colorSpace) {
+ switch (colorSpace) {
+ case "srgb":
+ img.colorSpace = ColorSpace.sRGB;
+ break;
+ case "cmyk":
+ img.colorSpace = ColorSpace.CMYK;
+ break;
+ case "adobe98":
+ img.colorSpace = ColorSpace.Adobe98;
+ break;
+ case "prophoto":
+ img.colorSpace = ColorSpace.ProPhoto;
+ break;
+ case "displayp3":
+ img.colorSpace = ColorSpace.DisplayP3;
+ break;
+ case "xyz":
+ img.colorSpace = ColorSpace.XYZ;
+ break;
+ case "lab":
+ img.colorSpace = ColorSpace.Lab;
+ break;
+ case "gray":
+ img.colorSpace = ColorSpace.Gray;
+ break;
+ // auto is default so do nothing
+ }
+ }
+ if (!transparency) {
+ img.backgroundColor = new MagickColor(0, 0, 0, 255); // TODO: probably make it an option to set the bg colour
+ img.alpha(AlphaAction.Remove);
+ }
+
+ log(
+ ["workers", "imagemagick"],
+ `Converting to ${fmt} with settings: ${JSON.stringify(conversionSettings)}`,
+ );
+
+ // magick-wasm automatically clamps (https://github.com/dlemstra/magick-wasm/blob/76fc6f2b0c0497d2ddc251bbf6174b4dc92ac3ea/src/magick-image.ts#L2480)
img.write(fmt as unknown as MagickFormat, (o: Uint8Array) => {
resolve(structuredClone(o));
});
diff --git a/static/sw.js b/static/sw.js
index 47573e2..75982f7 100644
--- a/static/sw.js
+++ b/static/sw.js
@@ -1,7 +1,7 @@
-const CACHE_NAME = "vert-wasm-cache-v2"; // updated when workers update
+const CACHE_NAME = "vert-wasm-cache-v3"; // updated when workers update
const WASM_FILES = [
- "/pandoc.wasm",
+ "/pandoc.wasm", // from https://github.com/haskell-wasm/pandoc-wasm
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/esm/ffmpeg-core.js",
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/esm/ffmpeg-core.wasm",
];