diff --git a/bun.lock b/bun.lock
index 5092015..4337d87 100644
--- a/bun.lock
+++ b/bun.lock
@@ -13,6 +13,8 @@
"@fontsource/radio-canada-big": "^5.2.7",
"@imagemagick/magick-wasm": "^0.0.37",
"@stripe/stripe-js": "^8.7.0",
+ "@mediabunny/ac3": "^1.35.1",
+ "@mediabunny/mp3-encoder": "^1.35.1",
"byte-data": "^19.0.1",
"client-zip": "^2.5.0",
"clsx": "^2.1.1",
@@ -20,6 +22,7 @@
"lucide-svelte": "^0.554.0",
"music-metadata": "^11.12.0",
"overlayscrollbars": "^2.14.0",
+ "mediabunny": "^1.35.1",
"overlayscrollbars-svelte": "^0.5.5",
"p-queue": "^9.1.0",
"riff-file": "^1.0.3",
@@ -175,6 +178,10 @@
"@lix-js/server-protocol-schema": ["@lix-js/server-protocol-schema@0.1.1", "", {}, "sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ=="],
+ "@mediabunny/ac3": ["@mediabunny/ac3@1.35.1", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-gLx3mFfs58/cdz2/f5Fp+6ZOrX5Jli3AZMXw/5EJcgm2VpnC/2oxtJyP1x/00PIS4UCE770slwIdz7U+2CQ31g=="],
+
+ "@mediabunny/mp3-encoder": ["@mediabunny/mp3-encoder@1.35.1", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-iY6FcPs7GbHMs/ASPmdzwojKcBN4AfMa+zFh4KNZNaLToyR7aEZILj9FsPVJA11bshaoo80dTaBcn69i33JHVA=="],
+
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
@@ -315,6 +322,10 @@
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
+ "@types/dom-mediacapture-transform": ["@types/dom-mediacapture-transform@0.1.11", "", { "dependencies": { "@types/dom-webcodecs": "*" } }, "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ=="],
+
+ "@types/dom-webcodecs": ["@types/dom-webcodecs@0.1.13", "", {}, "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ=="],
+
"@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
@@ -629,6 +640,8 @@
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
+ "mediabunny": ["mediabunny@1.35.1", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-VrprpjkLTZyIyhzBAc9D3HqgXarAE+le7+6x0Sdu9WN2SD86L8bUy0hz06Xwf14dVPqS7OwpY2KOhlUyqmI2eQ=="],
+
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
diff --git a/messages/en.json b/messages/en.json
index 82791cf..fdd4785 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -26,7 +26,7 @@
"audio": "Audio",
"documents": "Documents",
"video": "Video",
- "video_server_processing": "Server supported",
+ "video_server_processing": "Local & server supported",
"local_supported": "Local supported",
"status": {
"text": "Status: {status}",
diff --git a/package.json b/package.json
index 790a081..7f53746 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,8 @@
"@fontsource/radio-canada-big": "^5.2.7",
"@imagemagick/magick-wasm": "^0.0.37",
"@stripe/stripe-js": "^8.7.0",
+ "@mediabunny/ac3": "^1.35.1",
+ "@mediabunny/mp3-encoder": "^1.35.1",
"byte-data": "^19.0.1",
"client-zip": "^2.5.0",
"clsx": "^2.1.1",
@@ -54,6 +56,7 @@
"music-metadata": "^11.12.0",
"overlayscrollbars": "^2.14.0",
"overlayscrollbars-svelte": "^0.5.5",
+ "mediabunny": "^1.35.1",
"p-queue": "^9.1.0",
"riff-file": "^1.0.3",
"sanitize-html": "^2.17.0",
diff --git a/src/lib/converters/index.ts b/src/lib/converters/index.ts
index 11089ae..82c4a7b 100644
--- a/src/lib/converters/index.ts
+++ b/src/lib/converters/index.ts
@@ -5,6 +5,7 @@ import { PandocConverter } from "./pandoc.svelte";
import { VertdConverter } from "./vertd.svelte";
import { MagickConverter } from "./magick.svelte";
import { DISABLE_ALL_EXTERNAL_REQUESTS } from "$lib/util/consts";
+import { MediabunnyConverter } from "./mediabunny.svelte";
const getConverters = (): Converter[] => {
const converters: Converter[] = [
@@ -12,10 +13,12 @@ const getConverters = (): Converter[] => {
new FFmpegConverter(),
];
- if (!DISABLE_ALL_EXTERNAL_REQUESTS) {
+ if (DISABLE_ALL_EXTERNAL_REQUESTS) {
converters.push(new VertdConverter());
}
+ converters.push(new MediabunnyConverter());
+
converters.push(new PandocConverter());
return converters;
};
@@ -45,7 +48,7 @@ categories.audio.formats =
.map((f) => f.name) || [];
categories.video.formats =
converters
- .find((c) => c.name === "vertd")
+ .find((c) => c.name === "mediabunny")
?.supportedFormats.filter((f) => f.toSupported && f.isNative)
.map((f) => f.name) || [];
categories.image.formats =
diff --git a/src/lib/converters/mediabunny.svelte.ts b/src/lib/converters/mediabunny.svelte.ts
new file mode 100644
index 0000000..22c610e
--- /dev/null
+++ b/src/lib/converters/mediabunny.svelte.ts
@@ -0,0 +1,184 @@
+import { VertFile } from "$lib/types";
+import {
+ BlobSource,
+ BufferTarget,
+ canEncodeAudio,
+ Conversion,
+ Input,
+ MATROSKA,
+ MkvOutputFormat,
+ MovOutputFormat,
+ MP4,
+ Mp4OutputFormat,
+ MPEG_TS,
+ MpegTsOutputFormat,
+ Output,
+ QTFF,
+ WEBM,
+ WebMOutputFormat,
+} from "mediabunny";
+import { registerMp3Encoder } from "@mediabunny/mp3-encoder";
+import { registerAc3Decoder, registerAc3Encoder } from "@mediabunny/ac3";
+import { Converter, FormatInfo, type WorkerStatus } from "./converter.svelte";
+import { ToastManager } from "$lib/util/toast.svelte";
+import { error, log } from "$lib/util/logger";
+
+// codec compatibility object, based on docs
+// https://mediabunny.dev/guide/supported-formats-and-codecs#compatibility-table
+const codecCompatibility = {
+ video: {
+ mp4: ['avc', 'hevc', 'vp8', 'vp9', 'av1'],
+ m4v: ['avc', 'hevc', 'vp8', 'vp9', 'av1'],
+ f4v: ['avc', 'hevc', 'vp8', 'vp9', 'av1'],
+ '3gp': ['avc', 'hevc', 'vp8', 'vp9', 'av1'],
+ '3g2': ['avc', 'hevc', 'vp8', 'vp9', 'av1'],
+ mkv: ['avc', 'hevc', 'vp8', 'vp9', 'av1'],
+ webm: ['vp8', 'vp9', 'av1'],
+ mov: ['avc', 'hevc', 'vp8', 'vp9', 'av1'],
+ ts: ['avc', 'hevc'],
+ },
+ audio: {
+ mp4: ['aac', 'opus', 'mp3', 'vorbis', 'flac', 'ac3', 'eac3', 'pcm-s16', 'pcm-s16be', 'pcm-s24', 'pcm-s24be', 'pcm-s32', 'pcm-s32be', 'pcm-f32', 'pcm-f64'],
+ m4v: ['aac', 'opus', 'mp3', 'vorbis', 'flac', 'ac3', 'eac3', 'pcm-s16', 'pcm-s16be', 'pcm-s24', 'pcm-s24be', 'pcm-s32', 'pcm-s32be', 'pcm-f32', 'pcm-f64'],
+ f4v: ['aac', 'opus', 'mp3', 'vorbis', 'flac', 'ac3', 'eac3', 'pcm-s16', 'pcm-s16be', 'pcm-s24', 'pcm-s24be', 'pcm-s32', 'pcm-s32be', 'pcm-f32', 'pcm-f64'],
+ '3gp': ['aac', 'opus', 'mp3', 'vorbis', 'flac', 'ac3', 'eac3', 'pcm-s16', 'pcm-s16be', 'pcm-s24', 'pcm-s24be', 'pcm-s32', 'pcm-s32be', 'pcm-f32', 'pcm-f64'],
+ '3g2': ['aac', 'opus', 'mp3', 'vorbis', 'flac', 'ac3', 'eac3', 'pcm-s16', 'pcm-s16be', 'pcm-s24', 'pcm-s24be', 'pcm-s32', 'pcm-s32be', 'pcm-f32', 'pcm-f64'],
+ mkv: ['aac', 'opus', 'mp3', 'vorbis', 'flac', 'ac3', 'eac3', 'pcm-u8', 'pcm-s16', 'pcm-s24', 'pcm-s32', 'pcm-f32', 'pcm-f64'],
+ webm: ['opus', 'vorbis'],
+ mov: ['aac', 'opus', 'mp3', 'vorbis', 'flac', 'ac3', 'eac3', 'pcm-u8', 'pcm-s8', 'pcm-s16', 'pcm-s16be', 'pcm-s24', 'pcm-s24be', 'pcm-s32', 'pcm-s32be', 'pcm-f32', 'pcm-f32be', 'pcm-f64', 'ulaw', 'alaw'],
+ ts: ['aac', 'mp3', 'ac3', 'eac3'],
+ },
+} as const;
+
+export class MediabunnyConverter extends Converter {
+ public name = "mediabunny";
+ public status: WorkerStatus = $state("ready");
+ public reportsProgress: boolean = true;
+
+ private activeConversions = new Map
- {@html sanitize(m["upload.cards.status.text"]({ - status: getStatusText(s.status), - }))} + {@html sanitize( + m["upload.cards.status.text"]({ + status: getStatusText(s.status), + }), + )}