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':
|
||||
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
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
"settings": "settings",
|
||||
"updates": "updates",
|
||||
"donate": "donate",
|
||||
"about": "about"
|
||||
"about": "about",
|
||||
"remux": "remux"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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 @@
|
|||
<SidebarTab tabName="save" tabLink="/">
|
||||
<IconDownload />
|
||||
</SidebarTab>
|
||||
<SidebarTab tabName="remux" tabLink="/remux">
|
||||
<IconRepeat />
|
||||
</SidebarTab>
|
||||
<SidebarTab tabName="settings" tabLink={settingsLink}>
|
||||
<IconSettings />
|
||||
</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 { 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"]
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue