web: barebones core for ffmpeg & remux page
This commit is contained in:
parent
ebd6cc801b
commit
41a002929e
8 changed files with 213 additions and 4 deletions
|
@ -91,12 +91,27 @@ importers:
|
||||||
'@fontsource/ibm-plex-mono':
|
'@fontsource/ibm-plex-mono':
|
||||||
specifier: ^5.0.13
|
specifier: ^5.0.13
|
||||||
version: 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':
|
'@imput/version-info':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../packages/version-info
|
version: link:../packages/version-info
|
||||||
'@tabler/icons-svelte':
|
'@tabler/icons-svelte':
|
||||||
specifier: 3.6.0
|
specifier: 3.6.0
|
||||||
version: 3.6.0(svelte@4.2.18)
|
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:
|
sveltekit-i18n:
|
||||||
specifier: ^2.4.2
|
specifier: ^2.4.2
|
||||||
version: 2.4.2(svelte@4.2.18)
|
version: 2.4.2(svelte@4.2.18)
|
||||||
|
@ -502,6 +517,22 @@ packages:
|
||||||
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
|
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
|
||||||
deprecated: Use @eslint/object-schema instead
|
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':
|
'@isaacs/cliui@8.0.2':
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -756,6 +787,12 @@ packages:
|
||||||
'@ungap/structured-clone@1.2.0':
|
'@ungap/structured-clone@1.2.0':
|
||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
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:
|
accepts@1.3.8:
|
||||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
@ -2444,6 +2481,16 @@ snapshots:
|
||||||
|
|
||||||
'@humanwhocodes/object-schema@2.0.3': {}
|
'@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':
|
'@isaacs/cliui@8.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
|
@ -2701,6 +2748,10 @@ snapshots:
|
||||||
|
|
||||||
'@ungap/structured-clone@1.2.0': {}
|
'@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:
|
accepts@1.3.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
"settings": "settings",
|
"settings": "settings",
|
||||||
"updates": "updates",
|
"updates": "updates",
|
||||||
"donate": "donate",
|
"donate": "donate",
|
||||||
"about": "about"
|
"about": "about",
|
||||||
|
"remux": "remux"
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,13 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/noto-sans-mono": "^5.0.20",
|
"@fontsource-variable/noto-sans-mono": "^5.0.20",
|
||||||
"@fontsource/ibm-plex-mono": "^5.0.13",
|
"@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:^",
|
"@imput/version-info": "workspace:^",
|
||||||
"@tabler/icons-svelte": "3.6.0",
|
"@tabler/icons-svelte": "3.6.0",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^1.1.0",
|
||||||
"sveltekit-i18n": "^2.4.2",
|
"sveltekit-i18n": "^2.4.2",
|
||||||
"ts-deepmerge": "^7.0.0"
|
"ts-deepmerge": "^7.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
import IconDownload from "@tabler/icons-svelte/IconDownload.svelte";
|
import IconDownload from "@tabler/icons-svelte/IconDownload.svelte";
|
||||||
import IconSettings from "@tabler/icons-svelte/IconSettings.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 IconComet from "@tabler/icons-svelte/IconComet.svelte";
|
||||||
import IconHeart from "@tabler/icons-svelte/IconHeart.svelte";
|
import IconHeart from "@tabler/icons-svelte/IconHeart.svelte";
|
||||||
import IconInfoCircle from "@tabler/icons-svelte/IconInfoCircle.svelte";
|
import IconInfoCircle from "@tabler/icons-svelte/IconInfoCircle.svelte";
|
||||||
|
@ -27,6 +29,9 @@
|
||||||
<SidebarTab tabName="save" tabLink="/">
|
<SidebarTab tabName="save" tabLink="/">
|
||||||
<IconDownload />
|
<IconDownload />
|
||||||
</SidebarTab>
|
</SidebarTab>
|
||||||
|
<SidebarTab tabName="remux" tabLink="/remux">
|
||||||
|
<IconRepeat />
|
||||||
|
</SidebarTab>
|
||||||
<SidebarTab tabName="settings" tabLink={settingsLink}>
|
<SidebarTab tabName="settings" tabLink={settingsLink}>
|
||||||
<IconSettings />
|
<IconSettings />
|
||||||
</SidebarTab>
|
</SidebarTab>
|
||||||
|
|
63
web/src/lib/ffmpeg.ts
Normal file
63
web/src/lib/ffmpeg.ts
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
12
web/src/routes/ffmpeg-core.worker.js/+server.ts
Normal file
12
web/src/routes/ffmpeg-core.worker.js/+server.ts
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
55
web/src/routes/remux/+page.svelte
Normal file
55
web/src/routes/remux/+page.svelte
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import FFmpegWrapper from "$lib/ffmpeg";
|
||||||
|
|
||||||
|
import { openURL } from "$lib/download";
|
||||||
|
|
||||||
|
const loadFile = async() => {
|
||||||
|
const fileInput = document.createElement("input");
|
||||||
|
fileInput.type = "file";
|
||||||
|
fileInput.accept = "video/*,audio/*";
|
||||||
|
|
||||||
|
fileInput.onchange = async (e: Event) => {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
if (target.files?.length === 1) {
|
||||||
|
const file = target.files[0];
|
||||||
|
const type = file.type.split("/")[0];
|
||||||
|
const format = file.type.split("/")[1];
|
||||||
|
|
||||||
|
if (!["video", "audio"].includes(type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
|
||||||
|
const fileBlob = URL.createObjectURL(
|
||||||
|
new Blob([file], { type: "video/mp4" })
|
||||||
|
);
|
||||||
|
|
||||||
|
const ff = new FFmpegWrapper();
|
||||||
|
|
||||||
|
await ff.init();
|
||||||
|
const render = await ff.renderFile(fileBlob, type, format);
|
||||||
|
|
||||||
|
openURL(render);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileInput.click();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="remux-container">
|
||||||
|
<button on:click={() => loadFile()}>
|
||||||
|
load file
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#remux-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,9 +1,22 @@
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import { defineConfig, searchForWorkspaceRoot } from "vite";
|
import { defineConfig, searchForWorkspaceRoot } from "vite";
|
||||||
|
|
||||||
|
import basicSSL from "@vitejs/plugin-basic-ssl";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
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: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
@ -24,6 +37,10 @@ export default defineConfig({
|
||||||
allow: [
|
allow: [
|
||||||
searchForWorkspaceRoot(process.cwd())
|
searchForWorkspaceRoot(process.cwd())
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
}
|
proxy: {}
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ["@imput/ffmpeg.wasm"]
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue