mirror of
https://github.com/imputnet/cobalt.git
synced 2025-02-01 02:58:35 +00:00
web/remux: convert to a web worker (wip)
This commit is contained in:
parent
28d8927c08
commit
1ed2eef65a
|
@ -39,7 +39,7 @@ export default class LibAVWrapper {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await libav.ffprobe([
|
await libav.ffprobe([
|
||||||
'-v', 'quiet',
|
//'-v', 'quiet',
|
||||||
'-print_format', 'json',
|
'-print_format', 'json',
|
||||||
'-show_format',
|
'-show_format',
|
||||||
'-show_streams',
|
'-show_streams',
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const maskImage = async (source: Blob, mask: RawImage) => {
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeImageBackground = async (file: File) => {
|
const removeImageBackground = async (file: File) => {
|
||||||
const originalImageBlob = new Blob([file]);
|
const originalImageBlob = new Blob([file]);
|
||||||
const image = await RawImage.fromBlob(originalImageBlob);
|
const image = await RawImage.fromBlob(originalImageBlob);
|
||||||
|
|
||||||
|
|
89
web/src/lib/workers/remux.ts
Normal file
89
web/src/lib/workers/remux.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import mime from "mime";
|
||||||
|
import LibAVWrapper from "$lib/libav";
|
||||||
|
|
||||||
|
const error = (code: string) => {
|
||||||
|
return {
|
||||||
|
error: `error.${code}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ff = new LibAVWrapper((progress) => {
|
||||||
|
self.postMessage({
|
||||||
|
progress: {
|
||||||
|
durationProcessed: progress.out_time_sec,
|
||||||
|
speed: progress.speed,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
ff.init();
|
||||||
|
|
||||||
|
const remux = async (file: File) => {
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
await ff.init();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file_info = await ff.probe(file).catch((e) => {
|
||||||
|
if (e?.message?.toLowerCase().includes("out of memory")) {
|
||||||
|
console.error("uh oh! out of memory");
|
||||||
|
console.error(e);
|
||||||
|
|
||||||
|
self.postMessage(error("remux.out_of_resources"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!file_info?.format) {
|
||||||
|
self.postMessage(error("remux.corrupted"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.postMessage({
|
||||||
|
progressInfo: {
|
||||||
|
duration: Number(file_info.format.duration),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file instanceof File && !file.type) {
|
||||||
|
file = new File([file], file.name, {
|
||||||
|
type: mime.getType(file.name) ?? undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const render = await ff
|
||||||
|
.render({
|
||||||
|
blob: file,
|
||||||
|
args: ["-c", "copy", "-map", "0"],
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("uh-oh! render error");
|
||||||
|
console.error(e);
|
||||||
|
|
||||||
|
self.postMessage(error("remux.out_of_resources"));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!render) {
|
||||||
|
return console.log("not a valid file");
|
||||||
|
}
|
||||||
|
|
||||||
|
const filenameParts = file.name.split(".");
|
||||||
|
const filenameExt = filenameParts.pop();
|
||||||
|
|
||||||
|
const filename = `${filenameParts.join(".")} (remux).${filenameExt}`;
|
||||||
|
|
||||||
|
self.postMessage({
|
||||||
|
render,
|
||||||
|
filename
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = async (event: MessageEvent) => {
|
||||||
|
console.log(event.data);
|
||||||
|
|
||||||
|
if (event.data.file) {
|
||||||
|
await remux(event.data.file);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import mime from "mime";
|
import RemuxWorker from "$lib/workers/remux?worker";
|
||||||
import LibAVWrapper from "$lib/libav";
|
|
||||||
import { beforeNavigate, goto } from "$app/navigation";
|
import { beforeNavigate, goto } from "$app/navigation";
|
||||||
|
|
||||||
import { t } from "$lib/i18n/translations";
|
import { t } from "$lib/i18n/translations";
|
||||||
|
@ -19,145 +19,88 @@
|
||||||
let draggedOver = false;
|
let draggedOver = false;
|
||||||
let file: File | undefined;
|
let file: File | undefined;
|
||||||
|
|
||||||
let totalDuration: number | undefined;
|
|
||||||
let processedDuration: number | undefined;
|
|
||||||
|
|
||||||
let speed: number | undefined;
|
let speed: number | undefined;
|
||||||
let progress: string | undefined;
|
let progress: string | undefined;
|
||||||
|
let currentProgress: string | undefined;
|
||||||
|
|
||||||
let wentAway = false;
|
let wentAway = false;
|
||||||
|
|
||||||
$: {
|
|
||||||
if (totalDuration && processedDuration) {
|
|
||||||
const percentage = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(100, (processedDuration / totalDuration) * 100)
|
|
||||||
).toFixed(2);
|
|
||||||
progress = percentage;
|
|
||||||
} else if (processing) {
|
|
||||||
progress = undefined;
|
|
||||||
speed = undefined;
|
|
||||||
} else {
|
|
||||||
progress = undefined;
|
|
||||||
speed = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let processing = false;
|
let processing = false;
|
||||||
|
|
||||||
const ff = new LibAVWrapper((progress) => {
|
let worker: Worker;
|
||||||
if (progress.out_time_sec) {
|
|
||||||
processedDuration = progress.out_time_sec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress.speed) {
|
const remux = async () => {
|
||||||
speed = progress.speed;
|
worker = new RemuxWorker();
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ff.init();
|
|
||||||
|
|
||||||
const render = async () => {
|
|
||||||
if (!file || processing) return;
|
|
||||||
await ff.init();
|
|
||||||
|
|
||||||
let dialogOpened;
|
|
||||||
try {
|
|
||||||
progress = undefined;
|
|
||||||
speed = undefined;
|
speed = undefined;
|
||||||
|
progress = undefined;
|
||||||
|
currentProgress = undefined;
|
||||||
|
|
||||||
|
worker.postMessage({ file });
|
||||||
processing = true;
|
processing = true;
|
||||||
|
|
||||||
const file_info = await ff.probe(file).catch((e) => {
|
worker.onerror = (e) => {
|
||||||
if (e?.message?.toLowerCase().includes("out of memory")) {
|
console.error("remux worker exploded:", e);
|
||||||
console.error("uh oh! out of memory");
|
worker.terminate();
|
||||||
console.error(e);
|
};
|
||||||
|
|
||||||
dialogOpened = true;
|
worker.onmessage = (event) => {
|
||||||
return createDialog({
|
console.log(event.data);
|
||||||
id: "remux-error",
|
|
||||||
type: "small",
|
|
||||||
meowbalt: "error",
|
|
||||||
bodyText: $t("error.remux.out_of_resources"),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: $t("button.gotit"),
|
|
||||||
main: true,
|
|
||||||
action: () => {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!file_info?.format) {
|
if (event.data.progress) {
|
||||||
if (!dialogOpened)
|
let eprogress = event.data.progress;
|
||||||
return createDialog({
|
|
||||||
id: "remux-error",
|
if (eprogress?.speed) {
|
||||||
type: "small",
|
speed = eprogress.speed;
|
||||||
meowbalt: "error",
|
|
||||||
bodyText: $t("error.remux.corrupted"),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: $t("button.gotit"),
|
|
||||||
main: true,
|
|
||||||
action: () => {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
totalDuration = Number(file_info.format.duration);
|
if (eprogress?.durationProcessed) {
|
||||||
|
currentProgress = eprogress.durationProcessed;
|
||||||
if (file instanceof File && !file.type) {
|
|
||||||
file = new File([file], file.name, {
|
|
||||||
type: mime.getType(file.name) ?? undefined,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const render = await ff
|
if (eprogress?.duration && eprogress?.durationProcessed) {
|
||||||
.render({
|
progress = Math.max(
|
||||||
blob: file,
|
0,
|
||||||
args: ["-c", "copy", "-map", "0"],
|
Math.min(
|
||||||
})
|
100,
|
||||||
.catch((e) => {
|
(eprogress.durationProcessed / eprogress.duration) *
|
||||||
console.error("uh-oh! render error");
|
100
|
||||||
console.error(e);
|
)
|
||||||
return createDialog({
|
).toFixed(2);
|
||||||
id: "remux-error",
|
|
||||||
type: "small",
|
|
||||||
meowbalt: "error",
|
|
||||||
bodyText: $t("error.remux.out_of_resources"),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: $t("button.gotit"),
|
|
||||||
main: true,
|
|
||||||
action: () => {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!render) {
|
|
||||||
return console.log("not a valid file");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filenameParts = file.name.split(".");
|
console.log(eprogress, progress, speed, currentProgress);
|
||||||
const filenameExt = filenameParts.pop();
|
}
|
||||||
|
|
||||||
const filename = `${filenameParts.join(".")} (remux).${filenameExt}`;
|
if (event.data.render) {
|
||||||
|
processing = false;
|
||||||
return await downloadFile({
|
worker.terminate();
|
||||||
file: new File([render], filename, {
|
return downloadFile({
|
||||||
type: render.type,
|
file: new File([event.data.render], event.data.filename, {
|
||||||
|
type: event.data.render.type,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} finally {
|
|
||||||
processing = false;
|
|
||||||
file = undefined;
|
|
||||||
progress = undefined;
|
|
||||||
speed = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.data.error) {
|
||||||
|
processing = false;
|
||||||
|
worker.terminate();
|
||||||
|
|
||||||
|
return createDialog({
|
||||||
|
id: "remux-error",
|
||||||
|
type: "small",
|
||||||
|
meowbalt: "error",
|
||||||
|
bodyText: $t(event.data.error),
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: $t("button.gotit"),
|
||||||
|
main: true,
|
||||||
|
action: () => {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeNavigate((event) => {
|
beforeNavigate((event) => {
|
||||||
|
@ -183,7 +126,7 @@
|
||||||
main: true,
|
main: true,
|
||||||
color: "red",
|
color: "red",
|
||||||
action: async () => {
|
action: async () => {
|
||||||
await ff.terminate();
|
worker.terminate();
|
||||||
wentAway = true;
|
wentAway = true;
|
||||||
goto(path);
|
goto(path);
|
||||||
},
|
},
|
||||||
|
@ -193,10 +136,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$: if (file) {
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -235,6 +174,19 @@
|
||||||
"m4a",
|
"m4a",
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{#if file}
|
||||||
|
<div class="button-row">
|
||||||
|
<button on:click={remux}>remux</button>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
file = undefined;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
clear imported files
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="remux-bullets">
|
<div id="remux-bullets">
|
||||||
|
@ -272,11 +224,22 @@
|
||||||
<div class="progress-text">
|
<div class="progress-text">
|
||||||
processing ({progress}%, {speed}x)...
|
processing ({progress}%, {speed}x)...
|
||||||
</div>
|
</div>
|
||||||
|
{:else if currentProgress && speed}
|
||||||
|
<div class="progress-text">
|
||||||
|
processing ({currentProgress}s, {speed}x)...
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
processing...
|
<div class="progress-text">processing...</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
<button
|
||||||
done!
|
on:click={() => {
|
||||||
|
file = undefined;
|
||||||
|
processing = false;
|
||||||
|
worker?.terminate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
cancel
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -311,7 +274,6 @@
|
||||||
transition:
|
transition:
|
||||||
transform 0.2s,
|
transform 0.2s,
|
||||||
opacity 0.2s;
|
opacity 0.2s;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#remux-processing.processing {
|
#remux-processing.processing {
|
||||||
|
@ -331,6 +293,7 @@
|
||||||
padding: var(--padding);
|
padding: var(--padding);
|
||||||
gap: var(--padding);
|
gap: var(--padding);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
|
@ -348,6 +311,9 @@
|
||||||
|
|
||||||
#remux-receiver {
|
#remux-receiver {
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
#remux-bullets {
|
#remux-bullets {
|
||||||
|
@ -357,6 +323,18 @@
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 200px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 920px) {
|
@media screen and (max-width: 920px) {
|
||||||
#remux-open {
|
#remux-open {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
1
web/src/routes/remux/+page.ts
Normal file
1
web/src/routes/remux/+page.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const ssr = false;
|
Loading…
Reference in a new issue