From 41a002929e432622f9cc83eda8d8ec0d3320b730 Mon Sep 17 00:00:00 2001 From: wukko Date: Sat, 10 Aug 2024 17:21:39 +0600 Subject: [PATCH] web: barebones core for ffmpeg & remux page --- pnpm-lock.yaml | 51 +++++++++++++++ web/i18n/en/tabs.json | 3 +- web/package.json | 5 ++ web/src/components/sidebar/Sidebar.svelte | 5 ++ web/src/lib/ffmpeg.ts | 63 +++++++++++++++++++ .../routes/ffmpeg-core.worker.js/+server.ts | 12 ++++ web/src/routes/remux/+page.svelte | 55 ++++++++++++++++ web/vite.config.ts | 23 ++++++- 8 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 web/src/lib/ffmpeg.ts create mode 100644 web/src/routes/ffmpeg-core.worker.js/+server.ts create mode 100644 web/src/routes/remux/+page.svelte diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e077762..2e7cb7f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,12 +91,27 @@ importers: '@fontsource/ibm-plex-mono': specifier: ^5.0.13 version: 5.0.13 + '@imput/ffmpeg-core': + specifier: ^0.0.3 + version: 0.0.3 + '@imput/ffmpeg-types': + specifier: ^0.12.3 + version: 0.12.3 + '@imput/ffmpeg-util': + specifier: ^0.12.1 + version: 0.12.1 + '@imput/ffmpeg.wasm': + specifier: ^0.12.11 + version: 0.12.11 '@imput/version-info': specifier: workspace:^ version: link:../packages/version-info '@tabler/icons-svelte': specifier: 3.6.0 version: 3.6.0(svelte@4.2.18) + '@vitejs/plugin-basic-ssl': + specifier: ^1.1.0 + version: 1.1.0(vite@5.3.5(@types/node@20.14.14)) sveltekit-i18n: specifier: ^2.4.2 version: 2.4.2(svelte@4.2.18) @@ -502,6 +517,22 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@imput/ffmpeg-core@0.0.3': + resolution: {integrity: sha512-JIFUBzj1S2c2G2rhz4GlQD+rv8ftLhHwuyaG5TPesmGTrsgJjg+0CKAcjnirmpvwYZ4f/MLYprvAOVrNPLAbFA==} + engines: {node: '>=16.x'} + + '@imput/ffmpeg-types@0.12.3': + resolution: {integrity: sha512-Gi9d26eLEsI15Imt74tvWxPbsnyD0eWwrABd57cOjniIKCgAUTzFOXx4eHyj6CV/1WzQqDAr4haQk0XYCUtRqg==} + engines: {node: '>=16.x'} + + '@imput/ffmpeg-util@0.12.1': + resolution: {integrity: sha512-egmHSHEMgAxX0lqv6BiIP/ndPtjHm0LVJcrsO4P0T20dO9WLB8gMQLL6E3qmzJ8cpQgkG466f0OSGUNL/E70bg==} + engines: {node: '>=18.x'} + + '@imput/ffmpeg.wasm@0.12.11': + resolution: {integrity: sha512-/krk6BPy7TdDN73KHj/g0TVNDNLvvtlTv2T8di4GwrbuIcqfv5wCa2iMR2II/Vhf7zFYJvC2m5kDYHTtDQWS5Q==} + engines: {node: '>=18.x'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -756,6 +787,12 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vitejs/plugin-basic-ssl@1.1.0': + resolution: {integrity: sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==} + engines: {node: '>=14.6.0'} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -2444,6 +2481,16 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@imput/ffmpeg-core@0.0.3': {} + + '@imput/ffmpeg-types@0.12.3': {} + + '@imput/ffmpeg-util@0.12.1': {} + + '@imput/ffmpeg.wasm@0.12.11': + dependencies: + '@imput/ffmpeg-types': 0.12.3 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2701,6 +2748,10 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vitejs/plugin-basic-ssl@1.1.0(vite@5.3.5(@types/node@20.14.14))': + dependencies: + vite: 5.3.5(@types/node@20.14.14) + accepts@1.3.8: dependencies: mime-types: 2.1.35 diff --git a/web/i18n/en/tabs.json b/web/i18n/en/tabs.json index dcee6f6f..3cab9cc0 100644 --- a/web/i18n/en/tabs.json +++ b/web/i18n/en/tabs.json @@ -3,5 +3,6 @@ "settings": "settings", "updates": "updates", "donate": "donate", - "about": "about" + "about": "about", + "remux": "remux" } diff --git a/web/package.json b/web/package.json index b50062e4..bbf2247e 100644 --- a/web/package.json +++ b/web/package.json @@ -45,8 +45,13 @@ "dependencies": { "@fontsource-variable/noto-sans-mono": "^5.0.20", "@fontsource/ibm-plex-mono": "^5.0.13", + "@imput/ffmpeg-core": "^0.0.3", + "@imput/ffmpeg-types": "^0.12.3", + "@imput/ffmpeg-util": "^0.12.1", + "@imput/ffmpeg.wasm": "^0.12.11", "@imput/version-info": "workspace:^", "@tabler/icons-svelte": "3.6.0", + "@vitejs/plugin-basic-ssl": "^1.1.0", "sveltekit-i18n": "^2.4.2", "ts-deepmerge": "^7.0.0" } diff --git a/web/src/components/sidebar/Sidebar.svelte b/web/src/components/sidebar/Sidebar.svelte index eeaa0c4f..0c952bbb 100644 --- a/web/src/components/sidebar/Sidebar.svelte +++ b/web/src/components/sidebar/Sidebar.svelte @@ -7,6 +7,8 @@ import IconDownload from "@tabler/icons-svelte/IconDownload.svelte"; import IconSettings from "@tabler/icons-svelte/IconSettings.svelte"; + import IconRepeat from "@tabler/icons-svelte/IconRepeat.svelte"; + import IconComet from "@tabler/icons-svelte/IconComet.svelte"; import IconHeart from "@tabler/icons-svelte/IconHeart.svelte"; import IconInfoCircle from "@tabler/icons-svelte/IconInfoCircle.svelte"; @@ -27,6 +29,9 @@ + + + diff --git a/web/src/lib/ffmpeg.ts b/web/src/lib/ffmpeg.ts new file mode 100644 index 00000000..3e0b4638 --- /dev/null +++ b/web/src/lib/ffmpeg.ts @@ -0,0 +1,63 @@ +import ffmpegCore from "@imput/ffmpeg-core?url"; +import ffmpegCoreWASM from "@imput/ffmpeg-core/wasm?url"; + +import { FFmpeg } from "@imput/ffmpeg.wasm"; +import { fetchFile } from "@imput/ffmpeg-util"; + +export default class FFmpegWrapper { + initialized: boolean; + ffmpeg: FFmpeg; + concurrency: number; + + constructor() { + this.ffmpeg = new FFmpeg(); + this.initialized = false; + + this.concurrency = Math.min(4, navigator.hardwareConcurrency); + } + + async init() { + if (this.initialized) { + this.ffmpeg.terminate(); + } else { + this.initialized = true; + this.ffmpeg.on("log", ({ message }) => { + console.log(message); + }); + } + + await this.ffmpeg.load({ + coreURL: ffmpegCore, + wasmURL: ffmpegCoreWASM, + workerURL: "/ffmpeg-core.worker.js", + }); + } + + terminate() { + this.initialized = false; + return this.ffmpeg.terminate(); + } + + async renderFile(url: string, type: string, format: string) { + const input = `input.${format}`; + + await this.ffmpeg.writeFile( + input, + await fetchFile(url) + ) + + await this.ffmpeg.exec([ + '-threads', this.concurrency.toString(), + '-i', input, + '-c', 'copy', + `output.${format}` + ]); + + const data = await this.ffmpeg.readFile(`output.${format}`); + const finalBlob = URL.createObjectURL( + new Blob([data], { type: `${type}/${format}` }) + ); + + return finalBlob + } +} diff --git a/web/src/routes/ffmpeg-core.worker.js/+server.ts b/web/src/routes/ffmpeg-core.worker.js/+server.ts new file mode 100644 index 00000000..baa0e586 --- /dev/null +++ b/web/src/routes/ffmpeg-core.worker.js/+server.ts @@ -0,0 +1,12 @@ +// workaround so that vite doesn't fuck up the worker file +// and we can serve it from the same page at the same time + +import ffmpegCoreWorker from "@imput/ffmpeg-core/worker?raw"; + +export function GET() { + return new Response(ffmpegCoreWorker, { + headers: { + "Content-Type": "text/javascript" + } + }) +} diff --git a/web/src/routes/remux/+page.svelte b/web/src/routes/remux/+page.svelte new file mode 100644 index 00000000..e7f58ac0 --- /dev/null +++ b/web/src/routes/remux/+page.svelte @@ -0,0 +1,55 @@ + + +
+ +
+ + diff --git a/web/vite.config.ts b/web/vite.config.ts index d6663e0d..a9f2d49b 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,9 +1,22 @@ import { sveltekit } from "@sveltejs/kit/vite"; import { defineConfig, searchForWorkspaceRoot } from "vite"; +import basicSSL from "@vitejs/plugin-basic-ssl"; + export default defineConfig({ plugins: [ - sveltekit() + basicSSL(), + sveltekit(), + { + name: "isolation", + configureServer(server) { + server.middlewares.use((_req, res, next) => { + res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + next(); + }) + } + } ], build: { rollupOptions: { @@ -24,6 +37,10 @@ export default defineConfig({ allow: [ searchForWorkspaceRoot(process.cwd()) ] - } - } + }, + proxy: {} + }, + optimizeDeps: { + exclude: ["@imput/ffmpeg.wasm"] + }, });