diff --git a/messages/en.json b/messages/en.json
index e43a34b..33d91f3 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -129,7 +129,7 @@
"unavailable": "unavailable (is the url right?)",
"description": "The vertd project is a server wrapper for FFmpeg. This allows you to convert videos through the convenience of VERT's web interface, while still being able to harness the power of your GPU to do it as quickly as possible.",
"hosting_info": "We host a public instance for your convenience, but it is quite easy to host your own on your PC or server if you know what you are doing. You can download the server binaries [vertd_link]here[/vertd_link] - the process of setting this up will become easier in the future, so stay tuned!",
- "instance_url": "Instance URL",
+ "instance": "Instance",
"url_placeholder": "Example: http://localhost:24153",
"conversion_speed": "Conversion speed",
"speed_description": "This describes the tradeoff between speed and quality. Faster speeds will result in lower quality, but will get the job done quicker.",
@@ -140,7 +140,11 @@
"medium": "Medium",
"fast": "Fast",
"ultra_fast": "Ultra Fast"
- }
+ },
+ "auto_instance": "Auto (recommended)",
+ "eu_instance": "Falkenstein, Germany",
+ "us_instance": "Washington, USA",
+ "custom_instance": "Custom"
},
"privacy": {
"title": "Privacy",
diff --git a/messages/es.json b/messages/es.json
index a38a352..96b9fd0 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -109,7 +109,7 @@
"unavailable": "no disponible (¿has comprobado la url?)",
"description": "vertd es un proyecto que actúa como un servidor intermediario (\"wrapper\") para FFmpeg. Permite convertir vídeos sin dejar de lado la conveniente interfaz web de VERT y, a la vez, aprovecha la potencia de tu GPU para hacerlo lo más rápido posible.",
"hosting_info": "Alojamos una instancia pública para tu conveniencia, pero es bastante fácil alojar una propia en tu PC o servidor si sabes lo que estás haciendo. Puedes descargar los binarios del servidor [vertd_link]aquí[/vertd_link]. ¡El proceso de instalación será más fácil en el futuro, así que mantente atento!",
- "instance_url": "URL de la instancia",
+ "instance": "Instancia",
"url_placeholder": "Ejemplo: http://localhost:24153",
"conversion_speed": "Velocidad de conversión",
"speed_description": "Esto describe el equilibrio entre velocidad y calidad. Velocidades más rápidas resultarán en una calidad más baja, pero harán el trabajo más rápido.",
diff --git a/src/lib/converters/vertd.svelte.ts b/src/lib/converters/vertd.svelte.ts
index 2b5a11f..f5d39ea 100644
--- a/src/lib/converters/vertd.svelte.ts
+++ b/src/lib/converters/vertd.svelte.ts
@@ -1,5 +1,6 @@
import { error, log } from "$lib/logger";
import { Settings } from "$lib/sections/settings/index.svelte";
+import { VertdInstance } from "$lib/sections/settings/vertdSettings.svelte";
import { VertFile } from "$lib/types";
import { Converter, FormatInfo } from "./converter.svelte";
@@ -33,7 +34,7 @@ const vertdFetch = async (
url: U,
options: RequestInit,
): Promise => {
- const domain = Settings.instance.settings.vertdURL;
+ const domain = await VertdInstance.instance.url();
const res = await fetch(`${domain}${url}`, options);
const text = await res.text();
let json: VertdResponse = null!;
@@ -142,7 +143,7 @@ const progressEstimate = (
};
const uploadFile = async (file: VertFile): Promise => {
- const apiUrl = Settings.instance.settings.vertdURL;
+ const apiUrl = await VertdInstance.instance.url();
const formData = new FormData();
formData.append("file", file.file, file.name);
const xhr = new XMLHttpRequest();
@@ -274,10 +275,9 @@ export class VertdConverter extends Converter {
if (to.startsWith(".")) to = to.slice(1);
const uploadRes = await uploadFile(input);
- console.log(uploadRes);
+ const apiUrl = await VertdInstance.instance.url();
return new Promise((resolve, reject) => {
- const apiUrl = Settings.instance.settings.vertdURL;
const protocol = apiUrl.startsWith("https") ? "wss:" : "ws:";
const ws = new WebSocket(
`${protocol}//${apiUrl.replace("http://", "").replace("https://", "")}/api/ws`,
@@ -386,7 +386,7 @@ export class VertdConverter extends Converter {
}
public async valid(): Promise {
- if (!Settings.instance.settings.vertdURL) {
+ if (!(await VertdInstance.instance.url())) {
return false;
}
diff --git a/src/lib/ip.ts b/src/lib/ip.ts
new file mode 100644
index 0000000..7c37105
--- /dev/null
+++ b/src/lib/ip.ts
@@ -0,0 +1,33 @@
+export interface IpInfo {
+ ip: string;
+ network: string;
+ version: string;
+ city: string;
+ region: string;
+ region_code: string;
+ country: string;
+ country_name: string;
+ country_code: string;
+ country_code_iso3: string;
+ country_capital: string;
+ country_tld: string;
+ continent_code: string;
+ in_eu: boolean;
+ postal: string;
+ latitude: number;
+ longitude: number;
+ timezone: string;
+ utc_offset: string;
+ country_calling_code: string;
+ currency: string;
+ currency_name: string;
+ languages: string;
+ country_area: number;
+ country_population: number;
+ asn: string;
+ org: string;
+}
+
+export const ip = async (): Promise => {
+ return await fetch("https://ipapi.co/json/").then((r) => r.json());
+};
diff --git a/src/lib/sections/settings/Vertd.svelte b/src/lib/sections/settings/Vertd.svelte
index b680450..994b02b 100644
--- a/src/lib/sections/settings/Vertd.svelte
+++ b/src/lib/sections/settings/Vertd.svelte
@@ -8,7 +8,7 @@
import { vertdLoaded } from "$lib/store/index.svelte";
import { m } from "$lib/paraglide/messages";
import { link } from "$lib/store/index.svelte";
- //import { converters } from "$lib/converters";
+ import { VertdInstance, type VertdInner } from "./vertdSettings.svelte";
let vertdCommit = $state(null);
let abortController: AbortController | null = null;
@@ -16,37 +16,29 @@
const { settings = $bindable() }: { settings: ISettings } = $props();
$effect(() => {
- if (settings.vertdURL) {
- if (abortController) abortController.abort();
- abortController = new AbortController();
- const { signal } = abortController;
+ if (abortController) abortController.abort();
+ abortController = new AbortController();
+ const { signal } = abortController;
- vertdCommit = "loading";
- fetch(`${settings.vertdURL}/api/version`, { signal })
- .then((res) => {
- if (!res.ok) throw new Error("bad response");
+ vertdCommit = "loading";
+ VertdInstance.instance
+ .url()
+ .then((u) => fetch(`${u}/api/version`, { signal }))
+ .then((res) => {
+ if (!res.ok) throw new Error("bad response");
+ vertdLoaded.set(false);
+ return res.json();
+ })
+ .then((data) => {
+ vertdCommit = data.data;
+ vertdLoaded.set(true);
+ })
+ .catch((err) => {
+ if (err.name !== "AbortError") {
+ vertdCommit = null;
vertdLoaded.set(false);
- return res.json();
- })
- .then((data) => {
- vertdCommit = data.data;
- vertdLoaded.set(true);
- })
- .catch((err) => {
- if (err.name !== "AbortError") {
- vertdCommit = null;
- vertdLoaded.set(false);
- // const converter = converters.find((c) => c.name === "vertd");
- // if (converter) converter.status = "not-ready";
- }
- });
- } else {
- if (abortController) abortController.abort();
- vertdCommit = null;
- vertdLoaded.set(false);
- // const converter = converters.find((c) => c.name === "vertd");
- // if (converter) converter.status = "not-ready";
- }
+ }
+ });
return () => {
if (abortController) abortController.abort();
@@ -92,13 +84,60 @@
diff --git a/src/lib/sections/settings/index.svelte.ts b/src/lib/sections/settings/index.svelte.ts
index 6ef5f7f..a3eacc8 100644
--- a/src/lib/sections/settings/index.svelte.ts
+++ b/src/lib/sections/settings/index.svelte.ts
@@ -1,6 +1,7 @@
import { PUB_VERTD_URL } from "$env/static/public";
import type { ConversionBitrate } from "$lib/converters/ffmpeg.svelte";
import type { ConversionSpeed } from "$lib/converters/vertd.svelte";
+import { VertdInstance } from "./vertdSettings.svelte";
export { default as Appearance } from "./Appearance.svelte";
export { default as Conversion } from "./Conversion.svelte";
@@ -53,9 +54,11 @@ export class Settings {
public save() {
localStorage.setItem("settings", JSON.stringify(this.settings));
+ VertdInstance.instance.save();
}
public load() {
+ VertdInstance.instance.load();
const ls = localStorage.getItem("settings");
if (!ls) return;
const settings: ISettings = JSON.parse(ls);
diff --git a/src/lib/sections/settings/vertdSettings.svelte.ts b/src/lib/sections/settings/vertdSettings.svelte.ts
new file mode 100644
index 0000000..4ecf8cf
--- /dev/null
+++ b/src/lib/sections/settings/vertdSettings.svelte.ts
@@ -0,0 +1,120 @@
+import { ip, type IpInfo } from "$lib/ip";
+import { Settings } from "./index.svelte";
+
+const LOCATIONS = [
+ {
+ latitude: 49.0976,
+ longitude: 12.4869,
+ url: "https://eu.vertd.vert.sh",
+ },
+ {
+ latitude: 47.6587,
+ longitude: -117.426,
+ url: "https://usa.vertd.vert.sh",
+ },
+];
+
+const toRad = (value: number) => (value * Math.PI) / 180;
+const haversine = (lat1: number, lon1: number, lat2: number, lon2: number) => {
+ const R = 6371; // km
+ const dLat = toRad(lat2 - lat1);
+ const dLon = toRad(lon2 - lon1);
+ const a =
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(toRad(lat1)) *
+ Math.cos(toRad(lat2)) *
+ Math.sin(dLon / 2) *
+ Math.sin(dLon / 2);
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ const d = R * c;
+ return d;
+};
+
+export type VertdInner =
+ | { type: "auto" }
+ | { type: "eu" }
+ | { type: "us" }
+ | { type: "custom" };
+
+export class VertdInstance {
+ public static instance = new VertdInstance();
+
+ private cachedIp = $state(null);
+
+ private inner = $state({
+ type: "auto",
+ });
+
+ public save() {
+ localStorage.setItem("vertdInstance", JSON.stringify(this.inner));
+ }
+
+ public load() {
+ const ls = localStorage.getItem("vertdInstance");
+ if (!ls) return;
+ const inner: VertdInner = JSON.parse(ls);
+ this.inner = {
+ ...this.inner,
+ ...inner,
+ };
+ }
+
+ public innerData() {
+ return this.inner;
+ }
+
+ public set(inner: VertdInner) {
+ this.inner = inner;
+ this.save();
+ }
+
+ public async url() {
+ switch (this.inner.type) {
+ case "auto": {
+ if (!this.cachedIp) {
+ this.cachedIp = await ip();
+ }
+
+ return this.geographicallyOptimalInstance(this.cachedIp);
+ }
+
+ case "eu": {
+ return "https://eu.vertd.vert.sh";
+ }
+
+ case "us": {
+ return "https://usa.vertd.vert.sh";
+ }
+
+ case "custom": {
+ return Settings.instance.settings.vertdURL;
+ }
+ }
+ }
+
+ private geographicallyOptimalInstance(ip: IpInfo) {
+ let bestLocation = LOCATIONS[0];
+ let bestDistance = haversine(
+ ip.latitude,
+ ip.longitude,
+ bestLocation.latitude,
+ bestLocation.longitude,
+ );
+
+ for (let i = 1; i < LOCATIONS.length; i++) {
+ const location = LOCATIONS[i];
+ const distance = haversine(
+ ip.latitude,
+ ip.longitude,
+ location.latitude,
+ location.longitude,
+ );
+ if (distance < bestDistance) {
+ bestDistance = distance;
+ bestLocation = location;
+ }
+ }
+
+ return bestLocation.url;
+ }
+}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index ad75bbf..d603768 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -23,6 +23,7 @@
import { page } from "$app/state";
import { initStores as initAnimStores } from "$lib/animation/index.js";
import { locales, localizeHref } from "$lib/paraglide/runtime";
+ import { VertdInstance } from "$lib/sections/settings/vertdSettings.svelte.js";
let { children, data } = $props();
let enablePlausible = $state(false);
@@ -92,11 +93,12 @@
Settings.instance.load();
- fetch(`${Settings.instance.settings.vertdURL}/api/version`).then(
- (res) => {
+ VertdInstance.instance
+ .url()
+ .then((u) => fetch(`${u}/api/version`))
+ .then((res) => {
if (res.ok) $vertdLoaded = true;
- },
- );
+ });
return () => {
window.removeEventListener("paste", handlePaste);