feat: vertd custom headers (#180)

allows for adding custom headers to vertd requests. fixes #180
This commit is contained in:
Maya
2026-05-03 23:13:21 +03:00
parent a739dfb9dd
commit cf77cf6fd6
7 changed files with 243 additions and 113 deletions
+5
View File
@@ -289,6 +289,11 @@
"us": "Washington, USA",
"custom": "Custom"
},
"custom_headers": {
"label": "Custom headers",
"description": "Add custom headers to be sent with each request to the vertd instance, which could be used for authentication or other purposes (one per line).",
"placeholder": "Header-Name: Header Value"
},
"conversion_speed": {
"label": "Conversion speed",
"description": "This describes the tradeoff between speed and quality. Faster speeds will result in lower quality, but will get the job done quicker.",
+38 -12
View File
@@ -1,9 +1,14 @@
<script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements";
interface Props extends HTMLInputAttributes {
interface Props extends Omit<
HTMLInputAttributes,
keyof HTMLInputAttributes
> {
extension?: string;
prefix?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
let {
@@ -14,6 +19,7 @@
disabled = false,
extension,
prefix,
multiline = false,
...rest
}: Props = $props();
</script>
@@ -22,32 +28,52 @@
{#if type === "checkbox"}
<div class="relative w-full h-full">
<input
{...rest}
type="checkbox"
bind:checked
{disabled}
class="w-full p-3 rounded-lg bg-panel border-2 border-button
{...rest}
type="checkbox"
bind:checked
{disabled}
class="w-full p-3 rounded-lg bg-panel border-2 border-button
{prefix ? 'pl-[2rem]' : 'pl-3'}
{extension ? 'pr-[4rem]' : 'pr-3'}
{disabled && 'opacity-50 cursor-not-allowed'} appearance-none"
/>
{#if checked}
<div class="absolute w-7 h-7 inset-0 flex items-center justify-center pointer-events-none">
<svg class="w-6 h-6" fill="var(--bg-panel)" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
<div
class="absolute w-7 h-7 inset-0 flex items-center justify-center pointer-events-none"
>
<svg
class="w-6 h-6"
fill="var(--bg-panel)"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
</div>
{/if}
</div>
{:else if multiline}
<textarea
{...rest}
bind:value
{disabled}
class="w-full p-3 rounded-lg bg-panel border-2 border-button
{prefix ? 'pl-[2rem]' : 'pl-3'}
{extension ? 'pr-[4rem]' : 'pr-3'}
{disabled && 'opacity-50 cursor-not-allowed'}"
></textarea>
{:else}
<input
{...rest}
bind:value
{disabled}
class="w-full p-3 rounded-lg bg-panel border-2 border-button
{prefix ? 'pl-[2rem]' : 'pl-3'}
{extension ? 'pr-[4rem]' : 'pr-3'}
{disabled && 'opacity-50 cursor-not-allowed'}"
{prefix ? 'pl-[2rem]' : 'pl-3'}
{extension ? 'pr-[4rem]' : 'pr-3'}
{disabled && 'opacity-50 cursor-not-allowed'}"
/>
{/if}
@@ -218,7 +218,7 @@
placeholder={setting.placeholder}
disabled={disabled ||
setting.disabled}
oninput={(e) =>
oninput={(e: any) =>
handleSettingChange(
setting.customInputKey!,
e.currentTarget
@@ -238,7 +238,7 @@
] ??
setting.default}
placeholder={setting.placeholder}
onchange={(e) =>
onchange={(e: any) =>
handleSettingChange(
setting.key,
e.currentTarget.checked,
@@ -295,7 +295,7 @@
] ??
setting.default}
placeholder={setting.placeholder}
oninput={(e) =>
oninput={(e: any) =>
handleSettingChange(
setting.key,
e.currentTarget.value,
+19 -6
View File
@@ -2,7 +2,10 @@ import VertdErrorComponent from "$lib/components/functional/popups/VertdError.sv
import { error, log } from "$lib/util/logger";
import { m } from "$lib/paraglide/messages";
import { Settings } from "$lib/sections/settings/index.svelte";
import { VertdInstance } from "$lib/sections/settings/vertdSettings.svelte";
import {
VertdInstance,
getVertdCustomHeaders,
} from "$lib/sections/settings/vertdSettings.svelte";
import { VertFile } from "$lib/types";
import { Converter, FormatInfo } from "./converter.svelte";
import { PUB_DISABLE_FAILURE_BLOCKS } from "$env/static/public";
@@ -60,16 +63,18 @@ export const vertdFetch: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} = async (url: any, options: RequestInit, body?: any) => {
const domain = await VertdInstance.instance.url();
const headers = new Headers(options.headers);
for (const [key, value] of Object.entries(getVertdCustomHeaders()))
headers.set(key, value);
// if there is a body, insert a Content-Type: application/json header
if (body) {
options.headers = {
"Content-Type": "application/json",
...(options.headers || {}),
};
headers.set("Content-Type", "application/json");
options.body = JSON.stringify(body);
}
options.headers = headers;
const res = await fetch(domain + url, options);
const text = await res.text();
@@ -250,6 +255,7 @@ const createUploadTask = async (file: VertFile): Promise<UploadTask> => {
formData.append("file", file.file, file.name);
const xhr = new XMLHttpRequest();
xhr.open("POST", `${apiUrl}/api/upload`, true);
const customHeaders = getVertdCustomHeaders();
const promise = new Promise<UploadResponse>((resolve, reject) => {
xhr.upload.addEventListener("progress", (e) => {
@@ -285,6 +291,9 @@ const createUploadTask = async (file: VertFile): Promise<UploadTask> => {
reject(new Error("Conversion cancelled"));
};
for (const [key, value] of Object.entries(customHeaders))
xhr.setRequestHeader(key, value);
xhr.send(formData);
console.log("sent!");
});
@@ -299,6 +308,7 @@ const downloadFile = async (url: string, file: VertFile): Promise<Blob> => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "blob";
const customHeaders = getVertdCustomHeaders();
return new Promise((resolve, reject) => {
xhr.addEventListener("progress", (e) => {
@@ -322,6 +332,9 @@ const downloadFile = async (url: string, file: VertFile): Promise<Blob> => {
reject(xhr.statusText);
};
for (const [key, value] of Object.entries(customHeaders))
xhr.setRequestHeader(key, value);
xhr.send();
});
};
@@ -712,7 +725,7 @@ export class VertdConverter extends Converter {
// trim/crop/rotate - also have another ui for this prob
const animatedImages = [".gif", ".webp", ".apng"];
if (animatedImages.includes(input.from)) {
if (animatedImages.includes(input.to)) {
return [fps, resolution, metadata];
} else {
return [
+149 -89
View File
@@ -8,7 +8,11 @@
import { vertdLoaded } from "$lib/store/index.svelte";
import { m } from "$lib/paraglide/messages";
import { link, sanitize } from "$lib/store/index.svelte";
import { VertdInstance, type VertdInner } from "./vertdSettings.svelte";
import {
VertdInstance,
getVertdCustomHeaders,
type VertdInner,
} from "./vertdSettings.svelte";
import FancyInput from "$lib/components/functional/FancyInput.svelte";
let vertdCommit = $state<string | null>(null);
@@ -24,7 +28,12 @@
vertdCommit = "loading";
VertdInstance.instance
.url()
.then((u) => fetch(`${u}/api/version`, { signal }))
.then((u) =>
fetch(`${u}/api/version`, {
signal,
headers: getVertdCustomHeaders(),
}),
)
.then((res) => {
if (!res.ok) throw new Error("bad response");
vertdLoaded.set(false);
@@ -57,32 +66,41 @@
/>
{m["settings.vertd.title"]()}
</h2>
<p
class={clsx("text-sm font-normal", {
"text-failure": vertdCommit === null,
"text-green-700 dynadark:text-green-300": vertdCommit !== null,
"!text-muted": vertdCommit === "loading",
})}
>
{m["settings.vertd.status"]()}
{vertdCommit
? vertdCommit === "loading"
? m["settings.vertd.loading"]()
: m["settings.vertd.available"]({ commitId: vertdCommit })
: m["settings.vertd.unavailable"]()}
</p>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<p class="text-sm text-muted font-normal">
{@html sanitize(m["settings.vertd.description.main"]())}
</p>
<p class="text-sm text-muted font-normal">
{@html sanitize(link(
<div class="flex flex-col gap-4">
<p
class={clsx("text-sm font-normal", {
"text-failure": vertdCommit === null,
"text-green-700 dynadark:text-green-300":
vertdCommit !== null,
"!text-muted": vertdCommit === "loading",
})}
>
{m["settings.vertd.status"]()}
{vertdCommit
? vertdCommit === "loading"
? m["settings.vertd.loading"]()
: m["settings.vertd.available"]({
commitId: vertdCommit,
})
: m["settings.vertd.unavailable"]()}
</p>
<p class="text-sm text-muted font-normal">
{@html sanitize(m["settings.vertd.description.main"]())}
</p>
<p class="text-sm text-muted font-normal">
{@html sanitize(
link(
"vertd_link",
m["settings.vertd.description.info"](),
GITHUB_URL_VERTD,
))}
</p>
),
)}
</p>
</div>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<p class="text-base font-bold">
{m["settings.vertd.instance.label"]()}
@@ -135,75 +153,117 @@
{#if VertdInstance.instance.innerData().type === "custom"}
<FancyInput
type="text"
placeholder={m["settings.vertd.instance.url_placeholder"]()}
placeholder={m[
"settings.vertd.instance.url_placeholder"
]()}
bind:value={settings.vertdURL}
/>
{/if}
</div>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<p class="text-base font-bold">
{m["settings.vertd.conversion_speed.label"]()}
</p>
<p class="text-sm text-muted font-normal">
{m["settings.vertd.conversion_speed.description"]()}
</p>
</div>
<Dropdown
options={[
m["settings.vertd.conversion_speed.speeds.very_slow"](),
m["settings.vertd.conversion_speed.speeds.slower"](),
m["settings.vertd.conversion_speed.speeds.slow"](),
m["settings.vertd.conversion_speed.speeds.medium"](),
m["settings.vertd.conversion_speed.speeds.fast"](),
m["settings.vertd.conversion_speed.speeds.ultra_fast"](),
]}
settingsStyle
selected={(() => {
switch (settings.vertdSpeed) {
case "verySlow":
return m[
"settings.vertd.conversion_speed.speeds.very_slow"
]();
case "slower":
return m["settings.vertd.conversion_speed.speeds.slower"]();
case "slow":
return m["settings.vertd.conversion_speed.speeds.slow"]();
case "medium":
return m["settings.vertd.conversion_speed.speeds.medium"]();
case "fast":
return m["settings.vertd.conversion_speed.speeds.fast"]();
case "ultraFast":
return m[
"settings.vertd.conversion_speed.speeds.ultra_fast"
]();
}
})()}
onselect={(selected) => {
switch (selected) {
case m["settings.vertd.conversion_speed.speeds.very_slow"]():
settings.vertdSpeed = "verySlow";
break;
case m["settings.vertd.conversion_speed.speeds.slower"]():
settings.vertdSpeed = "slower";
break;
case m["settings.vertd.conversion_speed.speeds.slow"]():
settings.vertdSpeed = "slow";
break;
case m["settings.vertd.conversion_speed.speeds.medium"]():
settings.vertdSpeed = "medium";
break;
case m["settings.vertd.conversion_speed.speeds.fast"]():
settings.vertdSpeed = "fast";
break;
case m["settings.vertd.conversion_speed.speeds.ultra_fast"]():
settings.vertdSpeed = "ultraFast";
break;
}
}}
</div>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<p class="text-base font-bold">
{m["settings.vertd.conversion_speed.label"]()}
</p>
<p class="text-sm text-muted font-normal">
{m["settings.vertd.conversion_speed.description"]()}
</p>
</div>
<Dropdown
options={[
m["settings.vertd.conversion_speed.speeds.very_slow"](),
m["settings.vertd.conversion_speed.speeds.slower"](),
m["settings.vertd.conversion_speed.speeds.slow"](),
m["settings.vertd.conversion_speed.speeds.medium"](),
m["settings.vertd.conversion_speed.speeds.fast"](),
m[
"settings.vertd.conversion_speed.speeds.ultra_fast"
](),
]}
settingsStyle
selected={(() => {
switch (settings.vertdSpeed) {
case "verySlow":
return m[
"settings.vertd.conversion_speed.speeds.very_slow"
]();
case "slower":
return m[
"settings.vertd.conversion_speed.speeds.slower"
]();
case "slow":
return m[
"settings.vertd.conversion_speed.speeds.slow"
]();
case "medium":
return m[
"settings.vertd.conversion_speed.speeds.medium"
]();
case "fast":
return m[
"settings.vertd.conversion_speed.speeds.fast"
]();
case "ultraFast":
return m[
"settings.vertd.conversion_speed.speeds.ultra_fast"
]();
}
})()}
onselect={(selected) => {
switch (selected) {
case m[
"settings.vertd.conversion_speed.speeds.very_slow"
]():
settings.vertdSpeed = "verySlow";
break;
case m[
"settings.vertd.conversion_speed.speeds.slower"
]():
settings.vertdSpeed = "slower";
break;
case m[
"settings.vertd.conversion_speed.speeds.slow"
]():
settings.vertdSpeed = "slow";
break;
case m[
"settings.vertd.conversion_speed.speeds.medium"
]():
settings.vertdSpeed = "medium";
break;
case m[
"settings.vertd.conversion_speed.speeds.fast"
]():
settings.vertdSpeed = "fast";
break;
case m[
"settings.vertd.conversion_speed.speeds.ultra_fast"
]():
settings.vertdSpeed = "ultraFast";
break;
}
}}
/>
</div>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<p class="text-base font-bold">
{m["settings.vertd.custom_headers.label"]()}
</p>
<p class="text-sm text-muted font-normal">
{m["settings.vertd.custom_headers.description"]()}
</p>
<FancyInput
type="text"
bind:value={settings.vertdCustomHeaders}
placeholder={m[
"settings.vertd.custom_headers.placeholder"
]()}
multiline
/>
</div>
</div>
</div>
</div>
</Panel>
</div></Panel
>
+4 -2
View File
@@ -25,11 +25,12 @@ export interface ISettings {
plausible: boolean;
vertdURL: string;
vertdSpeed: ConversionSpeed; // videos
vertdBlockedHashes: Map<string, Date[]>; // hashes of files blocked from vertd conversion
vertdCustomHeaders: string; // custom headers to send to the vertd server
magickQuality: number; // images
ffmpegQuality: ConversionBitrate; // audio (or audio <-> video)
ffmpegSampleRate: string; // audio (or audio <-> video)
ffmpegCustomSampleRate: number; // audio (or audio <-> video) - only used when ffmpegSampleRate is "custom"
vertdBlockedHashes: Map<string, Date[]>; // hashes of files blocked from vertd conversion
}
export class Settings {
@@ -48,11 +49,12 @@ export class Settings {
plausible: true,
vertdURL: PUB_VERTD_URL,
vertdSpeed: "slow",
vertdBlockedHashes: new Map<string, Date[]>(),
vertdCustomHeaders: "",
magickQuality: 100,
ffmpegQuality: "auto",
ffmpegSampleRate: "auto",
ffmpegCustomSampleRate: 44100, //TODO: make string to match for vertd
vertdBlockedHashes: new Map<string, Date[]>(),
});
public save() {
@@ -13,6 +13,29 @@ export type VertdInner =
| { type: "us" }
| { type: "custom" };
export const getVertdCustomHeaders = (): Record<string, string> => {
const raw = Settings.instance.settings.vertdCustomHeaders.trim();
if (!raw) return {};
const headers: Record<string, string> = {};
for (const line of raw.split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed) continue;
const separatorIndex = trimmed.indexOf(":");
if (separatorIndex <= 0) continue;
const key = trimmed.slice(0, separatorIndex).trim();
const value = trimmed.slice(separatorIndex + 1).trim();
if (!key || !value) continue;
headers[key] = value;
}
return headers;
};
export class VertdInstance {
public static instance = new VertdInstance();
@@ -61,7 +84,8 @@ export class VertdInstance {
await fetch(url, {
method: "GET",
cache: "no-store",
mode: "no-cors",
mode: "cors",
headers: getVertdCustomHeaders(),
});
return performance.now() - start;
} catch {