diff --git a/src/lib/sections/settings/Privacy.svelte b/src/lib/sections/settings/Privacy.svelte
index 5748d5b..8df55dc 100644
--- a/src/lib/sections/settings/Privacy.svelte
+++ b/src/lib/sections/settings/Privacy.svelte
@@ -6,7 +6,7 @@
import { m } from "$lib/paraglide/messages";
import { link } from "$lib/store/index.svelte";
- const { settings }: { settings: ISettings } = $props();
+ const { settings = $bindable() }: { settings: ISettings } = $props();
diff --git a/src/lib/sections/settings/Vertd.svelte b/src/lib/sections/settings/Vertd.svelte
index 7963177..ff223c2 100644
--- a/src/lib/sections/settings/Vertd.svelte
+++ b/src/lib/sections/settings/Vertd.svelte
@@ -13,7 +13,7 @@
let vertdCommit = $state(null);
let abortController: AbortController | null = null;
- const { settings }: { settings: ISettings } = $props();
+ const { settings = $bindable() }: { settings: ISettings } = $props();
$effect(() => {
if (settings.vertdURL) {
diff --git a/src/lib/sections/settings/index.svelte.ts b/src/lib/sections/settings/index.svelte.ts
index ac902df..6ef5f7f 100644
--- a/src/lib/sections/settings/index.svelte.ts
+++ b/src/lib/sections/settings/index.svelte.ts
@@ -8,8 +8,17 @@ export { default as Vertd } from "./Vertd.svelte";
export { default as Privacy } from "./Privacy.svelte";
// TODO: clean up settings & button code (componetize)
+
+export interface DefaultFormats {
+ image: string;
+ video: string;
+ audio: string;
+ document: string;
+}
export interface ISettings {
filenameFormat: string;
+ defaultFormat: DefaultFormats;
+ useDefaultFormat: boolean;
metadata: boolean;
plausible: boolean;
vertdURL: string;
@@ -25,6 +34,13 @@ export class Settings {
public settings: ISettings = $state({
filenameFormat: "VERT_%name%",
+ defaultFormat: {
+ image: ".png",
+ video: ".mp4",
+ audio: ".mp3",
+ document: ".docx",
+ },
+ useDefaultFormat: false,
metadata: true,
plausible: true,
vertdURL: PUB_VERTD_URL,
diff --git a/src/lib/store/index.svelte.ts b/src/lib/store/index.svelte.ts
index 4d9f697..88bb358 100644
--- a/src/lib/store/index.svelte.ts
+++ b/src/lib/store/index.svelte.ts
@@ -50,9 +50,10 @@ class Files {
});
const cover = selectCover(common.picture);
if (cover) {
- const arrayBuffer = cover.data.buffer instanceof ArrayBuffer
- ? cover.data.buffer
- : new Uint8Array(cover.data).buffer;
+ const arrayBuffer =
+ cover.data.buffer instanceof ArrayBuffer
+ ? cover.data.buffer
+ : new Uint8Array(cover.data).buffer;
const blob = new Blob([new Uint8Array(arrayBuffer)], {
type: cover.format,
});
@@ -114,15 +115,23 @@ class Files {
? (mediaElement as HTMLVideoElement).videoHeight
: (mediaElement as HTMLImageElement).height;
- if (width === 0 || height === 0) {
- URL.revokeObjectURL(mediaElement.src);
- return undefined;
- }
-
const scale = Math.max(maxSize / width, maxSize / height);
canvas.width = width * scale;
canvas.height = height * scale;
ctx.drawImage(mediaElement, 0, 0, canvas.width, canvas.height);
+
+ // check if completely transparent
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ const isTransparent = Array.from(imageData.data).every(
+ (value, index) => {
+ return (index + 1) % 4 !== 0 || value === 0;
+ },
+ );
+ if (isTransparent) {
+ canvas.remove();
+ return undefined;
+ }
+
const url = canvas.toDataURL();
canvas.remove();
return url;
@@ -313,9 +322,9 @@ export const effects = writable(true);
export const theme = writable<"light" | "dark">("light");
export const locale = writable(getLocale());
export const availableLocales = {
- "en": "English",
- "es": "Español",
-}
+ en: "English",
+ es: "Español",
+};
export function updateLocale(newLocale: string) {
log(["locale"], `set to ${newLocale}`);
@@ -331,7 +340,7 @@ export function link(
text: string,
links: string | string[],
newTab?: boolean | boolean[],
- className?: string | string[]
+ className?: string | string[],
) {
if (!text) return "";
@@ -344,14 +353,17 @@ export function link(
tags.forEach((t, i) => {
const link = linksArr[i] ?? "#";
- const target = newTabArr[i] ? 'target="_blank" rel="noopener noreferrer"' : "";
+ const target = newTabArr[i]
+ ? 'target="_blank" rel="noopener noreferrer"'
+ : "";
const cls = classArr[i] ? `class="${classArr[i]}"` : "";
const regex = new RegExp(`\\[${t}\\](.*?)\\[\\/${t}\\]`, "g");
- result = result.replace(regex, (_, inner) =>
- `${inner}`
+ result = result.replace(
+ regex,
+ (_, inner) => `${inner}`,
);
});
return result;
-}
\ No newline at end of file
+}
diff --git a/src/routes/convert/+page.svelte b/src/routes/convert/+page.svelte
index 6a76aee..1460d04 100644
--- a/src/routes/convert/+page.svelte
+++ b/src/routes/convert/+page.svelte
@@ -27,21 +27,79 @@
RotateCwIcon,
XIcon,
} from "lucide-svelte";
- import { onMount } from "svelte";
import { m } from "$lib/paraglide/messages";
+ import { Settings } from "$lib/sections/settings/index.svelte";
+
+ let processedFileIds = $state(new Set());
+
+ $effect(() => {
+ if (!Settings.instance.settings || files.files.length === 0) return;
- onMount(() => {
- // depending on format, select right category and format
files.files.forEach((file) => {
+ const settings = Settings.instance.settings;
+ if (processedFileIds.has(file.id)) return;
+
const converter = file.findConverter();
- if (converter) {
- const category = Object.keys(categories).find((cat) =>
- categories[cat].formats.includes(file.to),
- );
- if (category) {
- file.to = file.to || categories[category].formats[0];
+ if (!converter) return;
+
+ let category: string | undefined;
+ const isImage = converters
+ .find((c) => c.name === "imagemagick")
+ ?.formatStrings((f) => f.fromSupported)
+ .includes(file.from);
+ const isAudio = converters
+ .find((c) => c.name === "ffmpeg")
+ ?.supportedFormats.filter((f) => f.isNative)
+ .map((f) => f.name)
+ .includes(file.from);
+ const isVideo = converters
+ .find((c) => c.name === "vertd")
+ ?.supportedFormats.filter((f) => f.isNative)
+ .map((f) => f.name)
+ .includes(file.from);
+ const isDocument = converters
+ .find((c) => c.name === "pandoc")
+ ?.supportedFormats.filter((f) => f.isNative)
+ .map((f) => f.name)
+ .includes(file.from);
+
+ if (isImage) category = "image";
+ else if (isAudio) category = "audio";
+ else if (isVideo) category = "video";
+ else if (isDocument) category = "doc";
+ if (!category) return;
+
+ let targetFormat: string | undefined;
+
+ // use default format if enabled
+ if (settings.useDefaultFormat) {
+ let defaultFormat: string | undefined;
+ const df = settings.defaultFormat;
+ if (category === "image") defaultFormat = df.image;
+ else if (category === "audio") defaultFormat = df.audio;
+ else if (category === "video") defaultFormat = df.video;
+ else if (category === "doc") defaultFormat = df.document;
+
+ if (
+ defaultFormat &&
+ defaultFormat !== file.from &&
+ categories[category]?.formats.includes(defaultFormat)
+ ) {
+ targetFormat = defaultFormat;
}
}
+
+ // else use first available format (or if default format is same as input)
+ if (!targetFormat) {
+ const firstDiff = categories[category]?.formats.find(
+ (f) => f !== file.from,
+ );
+ targetFormat =
+ firstDiff || categories[category]?.formats[0] || "";
+ }
+
+ file.to = targetFormat;
+ processedFileIds.add(file.id);
});
});
@@ -132,23 +190,38 @@
{#if !converters.length}
-
+
{:else if isAudio}
-
+
{:else if isVideo}
-
+
{:else if isDocument}
-
+
{:else}
-
+
{/if}
@@ -206,16 +279,18 @@
-
{m["convert.errors.cant_convert"]()}
+
+ {m["convert.errors.cant_convert"]()}
+
- {m["convert.errors.worker_downloading"]({
- type: isAudio
- ? m["convert.errors.audio"]()
- : isVideo
- ? "Video"
- : isDocument
- ? m["convert.errors.doc"]()
- : m["convert.errors.image"]()
+ {m["convert.errors.worker_downloading"]({
+ type: isAudio
+ ? m["convert.errors.audio"]()
+ : isVideo
+ ? "Video"
+ : isDocument
+ ? m["convert.errors.doc"]()
+ : m["convert.errors.image"](),
})}
@@ -223,16 +298,18 @@
-
{m["convert.errors.cant_convert"]()}
+
+ {m["convert.errors.cant_convert"]()}
+
- {m["convert.errors.worker_error"]({
- type: isAudio
- ? m["convert.errors.audio"]()
- : isVideo
- ? "Video"
- : isDocument
- ? m["convert.errors.doc"]()
- : m["convert.errors.image"]()
+ {m["convert.errors.worker_error"]({
+ type: isAudio
+ ? m["convert.errors.audio"]()
+ : isVideo
+ ? "Video"
+ : isDocument
+ ? m["convert.errors.doc"]()
+ : m["convert.errors.image"](),
})}
@@ -240,16 +317,18 @@
-
{m["convert.errors.cant_convert"]()}
+
+ {m["convert.errors.cant_convert"]()}
+
- {m["convert.errors.worker_timeout"]({
- type: isAudio
- ? m["convert.errors.audio"]()
- : isVideo
- ? "Video"
- : isDocument
- ? m["convert.errors.doc"]()
- : m["convert.errors.image"]()
+ {m["convert.errors.worker_timeout"]({
+ type: isAudio
+ ? m["convert.errors.audio"]()
+ : isVideo
+ ? "Video"
+ : isDocument
+ ? m["convert.errors.doc"]()
+ : m["convert.errors.image"](),
})}
@@ -257,7 +336,9 @@
-
{m["convert.errors.cant_convert"]()}
+
+ {m["convert.errors.cant_convert"]()}
+
{m["convert.errors.vertd_not_found"]()}
@@ -311,7 +392,10 @@
onselect={(option) => handleSelect(option, file)}
/>
-
+