feat: further optimisation for massive conversions

This commit is contained in:
not-nullptr 2025-03-20 16:14:16 +00:00
parent fb42680b40
commit e5df89ac57
3 changed files with 113 additions and 85 deletions

View File

@ -4,43 +4,41 @@
import Panel from "../visual/Panel.svelte"; import Panel from "../visual/Panel.svelte";
import Dropdown from "./Dropdown.svelte"; import Dropdown from "./Dropdown.svelte";
import Tooltip from "../visual/Tooltip.svelte"; import Tooltip from "../visual/Tooltip.svelte";
import ProgressBar from "../visual/ProgressBar.svelte";
import { fade } from "$lib/animation";
const length = $derived(files.files.length);
const progress = $derived(files.files.filter((f) => f.result).length);
</script> </script>
<Panel <Panel class="flex flex-col gap-4">
class="w-full h-auto flex items-center justify-between flex-col md:flex-row gap-4" <div
> class="w-full h-auto flex items-center justify-between flex-col md:flex-row gap-4"
<div class="flex items-center flex-col md:flex-row gap-2.5 max-md:w-full"> >
<button <div
onclick={() => files.convertAll()} class="flex items-center flex-col md:flex-row gap-2.5 max-md:w-full"
class="btn {$effects
? ''
: '!scale-100'} highlight flex gap-3 max-md:w-full"
disabled={!files.ready}
> >
<RefreshCw size="24" />
<p>Convert all</p>
</button>
<button
class="btn {$effects ? '' : '!scale-100'} flex gap-3 max-md:w-full"
disabled={!files.ready || !files.results}
onclick={() => files.downloadAll()}
>
<FolderArchiveIcon size="24" />
<p>Download all as .zip</p>
</button>
{#if $isMobile}
<button <button
class="btn p-4 {$effects onclick={() => files.convertAll()}
class="btn {$effects
? ''
: '!scale-100'} highlight flex gap-3 max-md:w-full"
disabled={!files.ready}
>
<RefreshCw size="24" />
<p>Convert all</p>
</button>
<button
class="btn {$effects
? '' ? ''
: '!scale-100'} flex gap-3 max-md:w-full" : '!scale-100'} flex gap-3 max-md:w-full"
disabled={files.files.length === 0} disabled={!files.ready || !files.results}
onclick={() => (files.files = [])} onclick={() => files.downloadAll()}
> >
<Trash2Icon size="24" /> <FolderArchiveIcon size="24" />
<p>Remove all files</p> <p>Download all as .zip</p>
</button> </button>
{:else} {#if $isMobile}
<Tooltip text="Remove all files" position="right">
<button <button
class="btn p-4 {$effects class="btn p-4 {$effects
? '' ? ''
@ -49,28 +47,53 @@
onclick={() => (files.files = [])} onclick={() => (files.files = [])}
> >
<Trash2Icon size="24" /> <Trash2Icon size="24" />
<p>Remove all files</p>
</button> </button>
</Tooltip> {:else}
{/if} <Tooltip text="Remove all files" position="right">
</div> <button
<div class="w-full bg-separator h-0.5 flex md:hidden"></div> class="btn p-4 {$effects
<div class="flex items-center gap-2"> ? ''
<p class="whitespace-nowrap text-xl">Set all to</p> : '!scale-100'} flex gap-3 max-md:w-full"
{#if files.requiredConverters.length === 1} disabled={files.files.length === 0}
<!-- cannot convert to svg or heif --> onclick={() => (files.files = [])}
{@const supported = files.files[0]?.converters >
.flatMap((c) => c.supportedFormats) <Trash2Icon size="24" />
?.filter((format) => format !== ".svg" && format !== ".heif")} </button>
<Dropdown </Tooltip>
onselect={(r) => {/if}
files.files.forEach((f) => { </div>
f.to = r; <div class="w-full bg-separator h-0.5 flex md:hidden"></div>
f.result = null; <div class="flex items-center gap-2">
})} <p class="whitespace-nowrap text-xl">Set all to</p>
options={supported || []} {#if files.requiredConverters.length === 1}
/> <!-- cannot convert to svg or heif -->
{:else} {@const supported = files.files[0]?.converters
<Dropdown options={["N/A"]} disabled /> .flatMap((c) => c.supportedFormats)
{/if} ?.filter(
(format) => format !== ".svg" && format !== ".heif",
)}
<Dropdown
onselect={(r) =>
files.files.forEach((f) => {
f.to = r;
f.result = null;
})}
options={supported || []}
/>
{:else}
<Dropdown options={["N/A"]} disabled />
{/if}
</div>
</div> </div>
{#if files.files.length > 50}
<div class="w-full px-2 flex gap-4 items-center">
<div class="flex-shrink-0 -mt-0.5 font-normal text-sm text-muted">
{progress}/{length}
</div>
<div class="flex-grow">
<ProgressBar min={0} max={length} {progress} />
</div>
</div>
{/if}
</Panel> </Panel>

View File

@ -23,43 +23,49 @@ class Files {
this.files.length === 0 ? false : this.files.every((f) => f.result), this.files.length === 0 ? false : this.files.every((f) => f.result),
); );
private _addThumbnail = async (file: VertFile) => { private thumbnailQueue = new PQueue({
const isAudio = converters concurrency: navigator.hardwareConcurrency || 4,
.find((c) => c.name === "ffmpeg") });
?.supportedFormats?.includes(file.from.toLowerCase());
const isVideo = converters
.find((c) => c.name === "vertd")
?.supportedFormats?.includes(file.from.toLowerCase());
try { private _addThumbnail = async (file: VertFile) => {
if (isAudio) { this.thumbnailQueue.add(async () => {
// try to get the thumbnail from the audio via music-metadata const isAudio = converters
const { common } = await parseBlob(file.file, { .find((c) => c.name === "ffmpeg")
skipPostHeaders: true, ?.supportedFormats?.includes(file.from.toLowerCase());
}); const isVideo = converters
const cover = selectCover(common.picture); .find((c) => c.name === "vertd")
if (cover) { ?.supportedFormats?.includes(file.from.toLowerCase());
const blob = new Blob([cover.data], {
type: cover.format, try {
if (isAudio) {
// try to get the thumbnail from the audio via music-metadata
const { common } = await parseBlob(file.file, {
skipPostHeaders: true,
}); });
file.blobUrl = URL.createObjectURL(blob); const cover = selectCover(common.picture);
if (cover) {
const blob = new Blob([cover.data], {
type: cover.format,
});
file.blobUrl = URL.createObjectURL(blob);
}
} else if (isVideo) {
// video
file.blobUrl = await this._generateThumbnailFromMedia(
file.file,
true,
);
} else {
// image
file.blobUrl = await this._generateThumbnailFromMedia(
file.file,
false,
);
} }
} else if (isVideo) { } catch (e) {
// video error(["files"], e);
file.blobUrl = await this._generateThumbnailFromMedia(
file.file,
true,
);
} else {
// image
file.blobUrl = await this._generateThumbnailFromMedia(
file.file,
false,
);
} }
} catch (e) { });
error(["files"], e);
}
}; };
private async _generateThumbnailFromMedia( private async _generateThumbnailFromMedia(

View File

@ -29,7 +29,6 @@ export class VertFile {
const converter = this.converters.filter((converter) => const converter = this.converters.filter((converter) =>
converter.supportedFormats.map((f) => supportedFormats.includes(f)), converter.supportedFormats.map((f) => supportedFormats.includes(f)),
); );
console.log(this.converters, supportedFormats);
return converter; return converter;
} }