Skip to content
Permalink
f681d64416
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
1 contributor

Users who have contributed to this file

388 lines (340 sloc) 10.7 KB
<script lang="ts">
import mime from "mime";
import LibAVWrapper from "$lib/libav";
import { beforeNavigate, goto } from "$app/navigation";
import { t } from "$lib/i18n/translations";
import { createDialog } from "$lib/state/dialogs";
import { downloadFile } from "$lib/download";
import Skeleton from "$components/misc/Skeleton.svelte";
import DropReceiver from "$components/misc/DropReceiver.svelte";
import FileReceiver from "$components/misc/FileReceiver.svelte";
import BulletExplain from "$components/misc/BulletExplain.svelte";
import IconRepeat from "@tabler/icons-svelte/IconRepeat.svelte";
import IconDevices from "@tabler/icons-svelte/IconDevices.svelte";
import IconInfoCircle from "@tabler/icons-svelte/IconInfoCircle.svelte";
let draggedOver = false;
let file: File | undefined;
let totalDuration: number | undefined;
let processedDuration: number | undefined;
let speed: number | undefined;
let progress: string | undefined;
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;
const ff = new LibAVWrapper((progress) => {
if (progress.out_time_sec) {
processedDuration = progress.out_time_sec;
}
if (progress.speed) {
speed = progress.speed;
}
});
ff.init();
const render = async () => {
if (!file || processing) return;
await ff.init();
let dialogOpened;
try {
progress = undefined;
speed = undefined;
processing = true;
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);
dialogOpened = true;
return createDialog({
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 (!dialogOpened)
return createDialog({
id: "remux-error",
type: "small",
meowbalt: "error",
bodyText: $t("error.remux.corrupted"),
buttons: [
{
text: $t("button.gotit"),
main: true,
action: () => {},
},
],
});
return;
}
totalDuration = 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);
return createDialog({
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(".");
const filenameExt = filenameParts.pop();
const filename = `${filenameParts.join(".")} (remux).${filenameExt}`;
return await downloadFile({
file: new File([render], filename, {
type: render.type,
}),
});
} finally {
processing = false;
file = undefined;
progress = undefined;
speed = undefined;
}
};
beforeNavigate((event) => {
if (processing && !wentAway) {
event.cancel();
const path = event.to?.route?.id;
if (path) {
return createDialog({
id: "remux-ongoing",
type: "small",
icon: "warn-red",
title: $t("dialog.processing.title.ongoing"),
bodyText: $t("dialog.processing.ongoing"),
buttons: [
{
text: $t("button.no"),
main: false,
action: () => {},
},
{
text: $t("button.yes"),
main: true,
color: "red",
action: async () => {
await ff.terminate();
wentAway = true;
goto(path);
},
},
],
});
}
}
});
$: if (file) {
render();
}
</script>
<svelte:head>
<title>{$t("tabs.remux")} ~ {$t("general.cobalt")}</title>
<meta
property="og:title"
content="{$t('tabs.remux')} ~ {$t('general.cobalt')}"
/>
</svelte:head>
<DropReceiver
bind:file
bind:draggedOver
id="remux-container"
classes={processing ? "processing" : ""}
>
<div
id="remux-open"
class:processing
tabindex="-1"
data-first-focus
data-focus-ring-hidden
>
<div id="remux-receiver">
<FileReceiver
bind:draggedOver
bind:file
acceptTypes={["video/*", "audio/*"]}
acceptExtensions={[
"mp4",
"webm",
"mp3",
"ogg",
"opus",
"wav",
"m4a",
]}
/>
</div>
<div id="remux-bullets">
<BulletExplain
title={$t("remux.bullet.purpose.title")}
description={$t("remux.bullet.purpose.description")}
icon={IconRepeat}
/>
<BulletExplain
title={$t("remux.bullet.explainer.title")}
description={$t("remux.bullet.explainer.description")}
icon={IconInfoCircle}
/>
<BulletExplain
title={$t("remux.bullet.privacy.title")}
description={$t("remux.bullet.privacy.description")}
icon={IconDevices}
/>
</div>
</div>
<div id="remux-processing" class:processing aria-hidden={!processing}>
<div id="processing-status">
{#if processing}
{#if progress && speed}
<div class="progress-bar">
<Skeleton
width="{progress}%"
height="20px"
class="elevated"
/>
</div>
<div class="progress-text">
processing ({progress}%, {speed}x)...
</div>
{:else}
processing...
{/if}
{:else}
done!
{/if}
</div>
</div>
</DropReceiver>
<style>
:global(#remux-container) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
#remux-open {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
text-align: center;
gap: 48px;
transition:
transform 0.2s,
opacity 0.2s;
}
#remux-processing {
position: absolute;
display: flex;
flex-direction: column;
opacity: 0;
transform: scale(0.9);
transition:
transform 0.2s,
opacity 0.2s;
pointer-events: none;
}
#remux-processing.processing {
opacity: 1;
transform: none;
}
#remux-open.processing {
transform: scale(0.9);
opacity: 0;
pointer-events: none;
}
#processing-status {
display: flex;
flex-direction: column;
padding: var(--padding);
gap: var(--padding);
justify-content: center;
}
.progress-bar {
height: 20px;
width: 400px;
max-width: 400px;
border-radius: 6px;
background: var(--button);
}
.progress-text {
font-size: 14px;
text-align: center;
}
#remux-receiver {
max-width: 450px;
}
#remux-bullets {
display: flex;
flex-direction: column;
gap: 18px;
max-width: 450px;
}
@media screen and (max-width: 920px) {
#remux-open {
flex-direction: column;
gap: var(--padding);
}
#remux-bullets {
padding: var(--padding);
}
}
@media screen and (max-width: 535px) {
.progress-bar {
width: 350px;
}
#remux-bullets {
gap: var(--padding);
}
}
@media screen and (max-height: 750px) and (max-width: 535px) {
:global(#remux-container:not(.processing)) {
justify-content: start;
align-items: start;
padding-top: var(--padding);
}
}
</style>