mirror of https://github.com/VERT-sh/VERT.git
feat: toast if file is too large for vertd
This commit is contained in:
parent
e01c7a4fde
commit
34a1a5df64
|
|
@ -174,6 +174,7 @@
|
|||
"vertd_details_error_message": "<b>Error message:</b> [view_link]View error logs[/view_link]",
|
||||
"vertd_details_close": "Close",
|
||||
"vertd_ratelimit": "Your video, '{filename}', has failed to convert a few times. To prevent server overload, further conversion attempts for this file have been temporarily blocked. Please try again later.",
|
||||
"vertd_file_too_large": "This file is too big for the specified vertd server ({fileSize}/{limit}). Please choose a smaller file or use another converter/server.",
|
||||
"vertd_retry": "Retrying video conversion for {filename} with different settings due to failure. This may take longer than usual.",
|
||||
"unsupported_format": "Only image, video, audio, and document files are supported",
|
||||
"format_output_only": "This format can currently only be used as output (converted to), not as input.",
|
||||
|
|
@ -382,7 +383,7 @@
|
|||
},
|
||||
"local_storage": {
|
||||
"title": "Local Storage",
|
||||
"description": "We use your browser's local storage to save your settings, and your browser's session storage to temporarily store the GitHub contributors list for the \"About\" section to reduce repeated GitHub API requests. No personal data is stored or transmitted.<br/><br/>The WebAssembly or browser versions of the conversion tools we use (Mediabunny, FFmpeg, ImageMagick, Pandoc) are also stored locally on your browser when you first visit the website, so you don't need to redownload them each visit. No personal data is stored or transmitted. You may view or delete this data at any time in the \"Privacy & data\" section in [settings_link]settings[/settings_link]."
|
||||
"description": "We use your browser's local storage to save your settings. We also use your browser's session storage to temporarily store the specified vertd server's file size limit (if applicable) to reduce repeated API requests and the GitHub contributors list for the \"About\" section to do the same. No personal data is stored or transmitted.<br/><br/>The WebAssembly or browser versions of the conversion tools we use (Mediabunny, FFmpeg, ImageMagick, Pandoc) are also stored locally on your browser when you first visit the website, so you don't need to redownload them each visit. No personal data is stored or transmitted. You may view or delete this data at any time in the \"Privacy & data\" section in [settings_link]settings[/settings_link]."
|
||||
},
|
||||
"contact": {
|
||||
"title": "Contact",
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
<!-- format options -->
|
||||
<!-- TODO: extract zip, image sequence & fps -->
|
||||
<!-- TODO: image sequence & fps -->
|
||||
{#if file?.name.toLowerCase().endsWith(".zip")}
|
||||
<div class="border-t border-separator text-base p-2">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -359,7 +359,6 @@ export class MediabunnyConverter extends Converter {
|
|||
label: m["convert.settings.video.video_codec"](),
|
||||
type: "select",
|
||||
default: "auto",
|
||||
// TODO: get supported from codecCompatibility based on output format
|
||||
options: [
|
||||
{ value: "auto", label: m["convert.settings.common.auto"]() },
|
||||
...supportedVideoCodecs.map((codec) => ({
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type {
|
|||
ConversionSettings,
|
||||
} from "$lib/types/conversion-settings";
|
||||
import { CONVERSION_BITRATES, SAMPLE_RATES } from "./ffmpeg.svelte";
|
||||
import { formatBytes } from "$lib/util/file";
|
||||
|
||||
interface UploadResponse {
|
||||
id: string;
|
||||
|
|
@ -38,6 +39,7 @@ interface CodecsResponse {
|
|||
interface RouteResponseMap {
|
||||
"/api/upload": UploadResponse;
|
||||
"/api/version": string;
|
||||
"/api/size_limit": number | null;
|
||||
"/api/keep": void;
|
||||
"/api/codecs": string[]; // get list of all codecs supported by vertd server
|
||||
[key: `/api/codecs/${string}`]: unknown; // list of codecs for this format
|
||||
|
|
@ -373,6 +375,54 @@ export class VertdConverter extends Converter {
|
|||
this.status = "ready";
|
||||
}
|
||||
|
||||
private async getServerSizeLimit(apiUrl: string): Promise<number | null> {
|
||||
const cacheKey = `vertd:size-limit:${apiUrl}`;
|
||||
|
||||
if (typeof sessionStorage !== "undefined") {
|
||||
try {
|
||||
const cached = sessionStorage.getItem(cacheKey);
|
||||
if (cached !== null) {
|
||||
const parsedCached = Number(cached);
|
||||
if (Number.isFinite(parsedCached) && parsedCached > 0) {
|
||||
this.log(
|
||||
`using cached vertd size limit: ${parsedCached} bytes`,
|
||||
);
|
||||
return parsedCached;
|
||||
}
|
||||
sessionStorage.removeItem(cacheKey);
|
||||
}
|
||||
} catch (e) {
|
||||
this.error(
|
||||
`failed to read vertd size limit from sessionStorage: ${e}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const limit = await vertdFetch("/api/size_limit", {
|
||||
method: "GET",
|
||||
});
|
||||
const parsed = Number(limit);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
||||
|
||||
if (typeof sessionStorage !== "undefined") {
|
||||
try {
|
||||
sessionStorage.setItem(cacheKey, parsed.toString());
|
||||
} catch (e) {
|
||||
this.error(
|
||||
`failed to cache vertd size limit in sessionStorage: ${e}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.log(`fetched vertd size limit: ${parsed} bytes`);
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
this.error(`failed to fetch vertd size limit: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private blocked(hash: string): boolean {
|
||||
let blockedHashes = Settings.instance.settings.vertdBlockedHashes;
|
||||
|
||||
|
|
@ -719,8 +769,22 @@ export class VertdConverter extends Converter {
|
|||
}
|
||||
}
|
||||
|
||||
const uploadRes = await uploadFile(fileUpload);
|
||||
const apiUrl = await VertdInstance.instance.url();
|
||||
const sizeLimit =
|
||||
(await this.getServerSizeLimit(apiUrl)) || Number.POSITIVE_INFINITY; // fall back to no limit just in case - server will block if too large anyways
|
||||
if (sizeLimit !== null && fileUpload.file.size > sizeLimit) {
|
||||
this.log(
|
||||
`blocked upload for ${input.name}: ${fileUpload.file.size} bytes exceeds server limit of ${sizeLimit} bytes`,
|
||||
);
|
||||
throw new Error(
|
||||
m["convert.errors.vertd_file_too_large"]({
|
||||
fileSize: formatBytes(fileUpload.file.size),
|
||||
limit: formatBytes(sizeLimit),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const uploadRes = await uploadFile(fileUpload);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
|
|
@ -794,14 +858,18 @@ export class VertdConverter extends Converter {
|
|||
this.error(
|
||||
`ws closed unexpectedly for file ${input.name} (code: ${event.code})`,
|
||||
);
|
||||
rejectConversion(new Error("vertd websocket closed unexpectedly"));
|
||||
rejectConversion(
|
||||
new Error("vertd websocket closed unexpectedly"),
|
||||
);
|
||||
};
|
||||
|
||||
ws.onmessage = async (e) => {
|
||||
let msg: VertdMessage;
|
||||
try {
|
||||
if (typeof e.data !== "string") {
|
||||
rejectConversion(new Error("invalid websocket payload type"));
|
||||
rejectConversion(
|
||||
new Error("invalid websocket payload type"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
msg = JSON.parse(e.data);
|
||||
|
|
|
|||
|
|
@ -85,18 +85,18 @@
|
|||
isLoadingCache = true;
|
||||
try {
|
||||
await swManager.clearCache();
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
if (typeof localStorage.clear === "function") {
|
||||
localStorage.clear();
|
||||
}
|
||||
if (typeof sessionStorage.clear === "function") {
|
||||
sessionStorage.clear();
|
||||
}
|
||||
|
||||
ToastManager.add({
|
||||
type: "success",
|
||||
message:
|
||||
m["settings.privacy.all_data_cleared"](),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = "/";
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
error(
|
||||
["privacy", "data"],
|
||||
|
|
@ -111,6 +111,9 @@
|
|||
});
|
||||
} finally {
|
||||
isLoadingCache = false;
|
||||
setTimeout(() => {
|
||||
window.location.href = "/";
|
||||
}, 1500);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -142,14 +145,18 @@
|
|||
{m["settings.privacy.plausible_title"]()}
|
||||
</p>
|
||||
<p class="text-sm text-muted font-normal">
|
||||
{@html sanitize(link(
|
||||
["plausible_link", "analytics_link"],
|
||||
m["settings.privacy.plausible_description"](),
|
||||
[
|
||||
"https://plausible.io/privacy-focused-web-analytics",
|
||||
"https://ats.vert.sh/vert.sh",
|
||||
],
|
||||
))}
|
||||
{@html sanitize(
|
||||
link(
|
||||
["plausible_link", "analytics_link"],
|
||||
m[
|
||||
"settings.privacy.plausible_description"
|
||||
](),
|
||||
[
|
||||
"https://plausible.io/privacy-focused-web-analytics",
|
||||
"https://ats.vert.sh/vert.sh",
|
||||
],
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 w-full">
|
||||
|
|
@ -270,7 +277,6 @@
|
|||
{m["settings.privacy.clear_all_data"]()}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div></Panel
|
||||
>
|
||||
|
|
|
|||
|
|
@ -196,8 +196,6 @@ export class VertFile {
|
|||
["file", "convert"],
|
||||
`no compatible converter found for ${this.from} to ${this.to}`,
|
||||
);
|
||||
// TODO: handle zip converter fallback explicitly if needed
|
||||
// TODO: provide a clearer error path for unsupported from/to pairs
|
||||
}
|
||||
} else {
|
||||
log(
|
||||
|
|
@ -250,8 +248,6 @@ export class VertFile {
|
|||
(c) => !this.attemptedConverters.has(c.name),
|
||||
);
|
||||
|
||||
// TODO: clean up languages file, then migrate all languages to new structure
|
||||
|
||||
// TODO: should figure out a cleaner way to do this
|
||||
if (!this.cancelled && nextConverter) {
|
||||
if (this.fallbackToastId !== null)
|
||||
|
|
|
|||
|
|
@ -74,3 +74,19 @@ export function formatFilename(format: string, file: File | string) {
|
|||
.replace(/%name%/g, baseName)
|
||||
.replace(/%extension%/g, originalExtension);
|
||||
}
|
||||
|
||||
export const formatBytes = (bytes: number): string => {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
|
||||
const units = ["KB", "MB", "GB", "TB"];
|
||||
let value = bytes;
|
||||
let unitIndex = -1;
|
||||
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return `${value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${units[unitIndex]}`;
|
||||
};
|
||||
|
|
@ -9,7 +9,10 @@
|
|||
import avatarRealmy from "$lib/assets/avatars/realmy.jpg";
|
||||
import avatarAzurejelly from "$lib/assets/avatars/azurejelly.jpg";
|
||||
import { PUB_DONATION_URL, PUB_STRIPE_KEY } from "$env/static/public";
|
||||
import { DISABLE_ALL_EXTERNAL_REQUESTS, GITHUB_API_URL } from "$lib/util/consts";
|
||||
import {
|
||||
DISABLE_ALL_EXTERNAL_REQUESTS,
|
||||
GITHUB_API_URL,
|
||||
} from "$lib/util/consts";
|
||||
import { m } from "$lib/paraglide/messages";
|
||||
import { ToastManager } from "$lib/util/toast.svelte";
|
||||
// import { dev } from "$app/environment";
|
||||
|
|
@ -69,9 +72,11 @@
|
|||
let ghContribs: Contributor[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
if (DISABLE_ALL_EXTERNAL_REQUESTS) {
|
||||
if (
|
||||
DISABLE_ALL_EXTERNAL_REQUESTS ||
|
||||
typeof sessionStorage === "undefined"
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the data is already in sessionStorage
|
||||
const cachedContribs = sessionStorage.getItem("ghContribs");
|
||||
|
|
@ -130,9 +135,8 @@
|
|||
}
|
||||
});
|
||||
|
||||
const donationsEnabled = PUB_STRIPE_KEY
|
||||
&& PUB_DONATION_URL
|
||||
&& !DISABLE_ALL_EXTERNAL_REQUESTS;
|
||||
const donationsEnabled =
|
||||
PUB_STRIPE_KEY && PUB_DONATION_URL && !DISABLE_ALL_EXTERNAL_REQUESTS;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full items-center">
|
||||
|
|
|
|||
Loading…
Reference in New Issue