mirror of https://github.com/VERT-sh/VERT.git
Merge branch 'nullptr/mediabunny-v2' into maya/conversion-things
This commit is contained in:
commit
343fb34a0e
13
bun.lock
13
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=="],
|
||||
|
|
|
|||
|
|
@ -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": "<b>Status:</b> {status}",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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<string, Conversion>();
|
||||
|
||||
public supportedFormats: FormatInfo[] = [
|
||||
new FormatInfo("mp4", true, true),
|
||||
new FormatInfo("m4v", true, true),
|
||||
new FormatInfo("mkv", true, true),
|
||||
new FormatInfo("webm", true, true),
|
||||
new FormatInfo("mov", true, true),
|
||||
|
||||
// mp4-based formats (should work)
|
||||
new FormatInfo("f4v", true, true),
|
||||
new FormatInfo("3gp", true, true),
|
||||
new FormatInfo("3g2", true, true),
|
||||
new FormatInfo("ts", true, true),
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// additional mediabunny coders
|
||||
// currently both official ones -- maybe add our own in the future
|
||||
this.initializeCodecs();
|
||||
}
|
||||
|
||||
private async initializeCodecs(): Promise<void> {
|
||||
if (!(await canEncodeAudio("mp3"))) {
|
||||
// Only register the custom encoder if there's no native support
|
||||
registerMp3Encoder();
|
||||
}
|
||||
registerAc3Decoder();
|
||||
registerAc3Encoder();
|
||||
}
|
||||
|
||||
public async convert(file: VertFile, to: string): Promise<VertFile> {
|
||||
const input = new Input({
|
||||
// TODO: add settings & special handling for certain formats & codecs
|
||||
formats: [MP4, QTFF, MATROSKA, WEBM, MPEG_TS],
|
||||
source: new BlobSource(file.file),
|
||||
});
|
||||
|
||||
const output = new Output({
|
||||
format: this.format(to),
|
||||
target: new BufferTarget(),
|
||||
});
|
||||
|
||||
const conversion = await Conversion.init({
|
||||
input,
|
||||
output,
|
||||
});
|
||||
|
||||
if (!conversion.isValid) {
|
||||
for (const discarded of conversion.discardedTracks) {
|
||||
ToastManager.add({
|
||||
type: "error",
|
||||
message: `Mediabunny discarded unsupported track: ${discarded.reason}`,
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Mediabunny conversion not valid`);
|
||||
}
|
||||
|
||||
conversion.onProgress = (progress) => {
|
||||
file.progress = progress * 100;
|
||||
};
|
||||
|
||||
this.activeConversions.set(file.id, conversion);
|
||||
await conversion.execute();
|
||||
this.activeConversions.delete(file.id);
|
||||
|
||||
if (!output.target.buffer) {
|
||||
throw new Error("Mediabunny conversion failed: no output buffer");
|
||||
}
|
||||
|
||||
const toFormat = to.startsWith(".") ? to.slice(1) : to;
|
||||
const originalName = file.file.name.split(".").slice(0, -1).join(".");
|
||||
const f = new File(
|
||||
[output.target.buffer],
|
||||
`${originalName}.${toFormat}`,
|
||||
{
|
||||
type: "application/octet-stream",
|
||||
},
|
||||
);
|
||||
|
||||
return new VertFile(f, toFormat);
|
||||
}
|
||||
|
||||
private format(ext: string) {
|
||||
switch (ext) {
|
||||
// i'm seeing this "ISMV" format from microsoft, so maybe?
|
||||
case ".mp4":
|
||||
case ".m4v":
|
||||
case ".f4v":
|
||||
case ".3gp":
|
||||
case ".3g2":
|
||||
return new Mp4OutputFormat();
|
||||
case ".mkv":
|
||||
return new MkvOutputFormat();
|
||||
case ".webm":
|
||||
return new WebMOutputFormat();
|
||||
case ".mov":
|
||||
return new MovOutputFormat();
|
||||
case ".ts":
|
||||
return new MpegTsOutputFormat(); // FIXME: audio tracks discarded - prob needs another audio codec
|
||||
default:
|
||||
throw new Error(`Unsupported format: ${ext}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async cancel(input: VertFile): Promise<void> {
|
||||
const conversion = this.activeConversions.get(input.id);
|
||||
if (!conversion) {
|
||||
error(
|
||||
["converters", this.name],
|
||||
`no active conversion found for file ${input.name}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
log(
|
||||
["converters", this.name],
|
||||
`cancelling conversion for file ${input.name}`,
|
||||
);
|
||||
|
||||
conversion.cancel();
|
||||
this.activeConversions.delete(input.id);
|
||||
}
|
||||
}
|
||||
|
|
@ -66,10 +66,20 @@
|
|||
};
|
||||
|
||||
if (!DISABLE_ALL_EXTERNAL_REQUESTS) {
|
||||
const formats = Array.from(
|
||||
new Set([
|
||||
...getSupportedFormats("vertd").split(", "),
|
||||
...getSupportedFormats("mediabunny").split(", "),
|
||||
]),
|
||||
)
|
||||
.filter((f) => f !== "none")
|
||||
.join(", ");
|
||||
|
||||
output.Video = {
|
||||
formats: getSupportedFormats("vertd"),
|
||||
formats,
|
||||
icon: Film,
|
||||
title: m["upload.cards.video"](),
|
||||
// TODO: add "partial" state? somehow figure out diff between vertd and mediabunny
|
||||
status: $vertdLoaded === true ? "ready" : "not-ready", // not using converter.status for this
|
||||
};
|
||||
}
|
||||
|
|
@ -231,9 +241,11 @@
|
|||
</p>
|
||||
{/if}
|
||||
<p>
|
||||
{@html sanitize(m["upload.cards.status.text"]({
|
||||
status: getStatusText(s.status),
|
||||
}))}
|
||||
{@html sanitize(
|
||||
m["upload.cards.status.text"]({
|
||||
status: getStatusText(s.status),
|
||||
}),
|
||||
)}
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col items-center relative"
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -16,6 +16,18 @@
|
|||
"src": "lettermark.jpg",
|
||||
"sizes": "512x512",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
{
|
||||
"src": "lettermark_maskable.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "lettermark_maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue