feat: conversion resolver improvements

This commit is contained in:
not-nullptr 2025-04-14 20:06:57 +01:00
parent 61c548eed9
commit b89e55c997
12 changed files with 125 additions and 90 deletions

View File

@ -69,7 +69,7 @@
{#if files.requiredConverters.length === 1} {#if files.requiredConverters.length === 1}
<!-- cannot convert to svg or heif --> <!-- cannot convert to svg or heif -->
{@const supported = files.files[0]?.converters {@const supported = files.files[0]?.converters
.flatMap((c) => c.supportedFormats) .flatMap((c) => c.formatStrings((f) => f.toSupported))
?.filter( ?.filter(
(format) => format !== ".svg" && format !== ".heif", (format) => format !== ".svg" && format !== ".heif",
)} )}

View File

@ -62,8 +62,8 @@
: 'justify-center'} overflow-hidden relative cursor-pointer {settingsStyle : 'justify-center'} overflow-hidden relative cursor-pointer {settingsStyle
? 'px-4' ? 'px-4'
: 'px-3'} py-3.5 bg-button {disabled : 'px-3'} py-3.5 bg-button {disabled
? 'opacity-50' ? 'opacity-50 cursor-auto'
: ''} flex items-center {settingsStyle : 'cursor-pointer'} flex items-center {settingsStyle
? 'rounded-xl' ? 'rounded-xl'
: 'rounded-full'} focus:!outline-none" : 'rounded-full'} focus:!outline-none"
onclick={toggle} onclick={toggle}

View File

@ -31,7 +31,7 @@
) )
).filter((c) => typeof c !== "undefined"); ).filter((c) => typeof c !== "undefined");
acceptedTypes = filteredConverters acceptedTypes = filteredConverters
.map((c) => c.supportedFormats.join(",")) .map((c) => c.formatStrings((f) => f.fromSupported).join(","))
.join(","); .join(",");
}; };

View File

@ -1,5 +1,24 @@
import type { VertFile } from "$lib/types"; import type { VertFile } from "$lib/types";
export class FormatInfo {
public name: string;
constructor(
name: string,
public fromSupported: boolean,
public toSupported: boolean,
) {
this.name = name;
if (!this.name.startsWith(".")) {
this.name = `.${this.name}`;
}
if (!this.fromSupported && !this.toSupported) {
throw new Error("Format must support at least one direction");
}
}
}
/** /**
* Base class for all converters. * Base class for all converters.
*/ */
@ -11,7 +30,7 @@ export class Converter {
/** /**
* List of supported formats. * List of supported formats.
*/ */
public supportedFormats: string[] = []; public supportedFormats: FormatInfo[] = [];
/** /**
* Convert a file to a different format. * Convert a file to a different format.
* @param input The input file. * @param input The input file.
@ -34,4 +53,11 @@ export class Converter {
public async valid(): Promise<boolean> { public async valid(): Promise<boolean> {
return true; return true;
} }
public formatStrings(predicate?: (f: FormatInfo) => boolean) {
if (predicate) {
return this.supportedFormats.filter(predicate).map((f) => f.name);
}
return this.supportedFormats.map((f) => f.name);
}
} }

View File

@ -1,5 +1,5 @@
import { VertFile } from "$lib/types"; import { VertFile } from "$lib/types";
import { Converter } from "./converter.svelte"; import { Converter, FormatInfo } from "./converter.svelte";
import { FFmpeg } from "@ffmpeg/ffmpeg"; import { FFmpeg } from "@ffmpeg/ffmpeg";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { error, log } from "$lib/logger"; import { error, log } from "$lib/logger";
@ -11,17 +11,17 @@ export class FFmpegConverter extends Converter {
public ready = $state(false); public ready = $state(false);
public supportedFormats = [ public supportedFormats = [
".mp3", new FormatInfo("mp3", true, true),
".wav", new FormatInfo("wav", true, true),
".flac", new FormatInfo("flac", true, true),
".ogg", new FormatInfo("ogg", true, true),
".aac", new FormatInfo("aac", true, true),
".m4a", new FormatInfo("m4a", true, true),
".wma", new FormatInfo("wma", true, true),
".amr", new FormatInfo("amr", true, true),
".ac3", new FormatInfo("ac3", true, true),
".alac", new FormatInfo("alac", true, true),
".aiff", new FormatInfo("aiff", true, true),
]; ];
public readonly reportsProgress = true; public readonly reportsProgress = true;

View File

@ -1,5 +1,5 @@
import { VertFile } from "$lib/types"; import { VertFile } from "$lib/types";
import { Converter } from "./converter.svelte"; import { Converter, FormatInfo } from "./converter.svelte";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import PandocWorker from "$lib/workers/pandoc?worker"; import PandocWorker from "$lib/workers/pandoc?worker";
@ -61,23 +61,19 @@ export class PandocConverter extends Converter {
); );
} }
// public name = "pandoc";
// public ready = $state(false);
// public wasm: ArrayBuffer = null!;
public supportedFormats = [ public supportedFormats = [
".docx", new FormatInfo("docx", true, true),
".doc", new FormatInfo("doc", true, true),
".md", new FormatInfo("md", true, true),
".html", new FormatInfo("html", true, true),
".rtf", new FormatInfo("rtf", true, true),
".csv", new FormatInfo("csv", true, true),
".tsv", new FormatInfo("tsv", true, true),
".json", new FormatInfo("json", true, true),
".rst", new FormatInfo("rst", true, true),
".epub", new FormatInfo("epub", true, true),
".odt", new FormatInfo("odt", true, true),
".docbook", new FormatInfo("docbook", true, true),
]; ];
} }

View File

@ -1,7 +1,7 @@
import { log } from "$lib/logger"; import { log } from "$lib/logger";
import { Settings } from "$lib/sections/settings/index.svelte"; import { Settings } from "$lib/sections/settings/index.svelte";
import { VertFile } from "$lib/types"; import { VertFile } from "$lib/types";
import { Converter } from "./converter.svelte"; import { Converter, FormatInfo } from "./converter.svelte";
interface VertdError { interface VertdError {
type: "error"; type: "error";
@ -200,16 +200,18 @@ export class VertdConverter extends Converter {
public name = "vertd"; public name = "vertd";
public ready = $state(false); public ready = $state(false);
public reportsProgress = true; public reportsProgress = true;
public supportedFormats = [ public supportedFormats = [
".mkv", new FormatInfo("mkv", true, true),
".mp4", new FormatInfo("mp4", true, true),
".webm", new FormatInfo("webm", true, true),
".avi", new FormatInfo("avi", true, true),
".wmv", new FormatInfo("wmv", true, true),
".mov", new FormatInfo("mov", true, true),
".gif", new FormatInfo("gif", true, true),
".mts", new FormatInfo("mts", true, true),
]; ];
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
private log: (...msg: any[]) => void = () => {}; private log: (...msg: any[]) => void = () => {};

View File

@ -4,7 +4,7 @@ import { addToast } from "$lib/store/ToastProvider";
import type { OmitBetterStrict, WorkerMessage } from "$lib/types"; import type { OmitBetterStrict, WorkerMessage } from "$lib/types";
import { VertFile } from "$lib/types"; import { VertFile } from "$lib/types";
import VipsWorker from "$lib/workers/vips?worker&url"; import VipsWorker from "$lib/workers/vips?worker&url";
import { Converter } from "./converter.svelte"; import { Converter, FormatInfo } from "./converter.svelte";
export class VipsConverter extends Converter { export class VipsConverter extends Converter {
private worker: Worker = browser private worker: Worker = browser
@ -15,34 +15,32 @@ export class VipsConverter extends Converter {
private id = 0; private id = 0;
public name = "libvips"; public name = "libvips";
public ready = $state(false); public ready = $state(false);
public static supportedFormatsStatic = [
...new Set([
".png",
".jpeg",
".jpg",
".webp",
".gif",
".ico",
".cur",
".ani",
".heic",
".hdr",
".jpe",
".dng",
".mat",
".pbm",
".pfm",
".pgm",
".pnm",
".ppm",
".raw",
".tif",
".tiff",
".jfif",
]),
];
public supportedFormats = VipsConverter.supportedFormatsStatic; public supportedFormats = [
new FormatInfo("png", true, true),
new FormatInfo("jpeg", true, true),
new FormatInfo("jpg", true, true),
new FormatInfo("webp", true, true),
new FormatInfo("gif", true, true),
new FormatInfo("ico", true, false),
new FormatInfo("cur", true, false),
new FormatInfo("ani", true, false),
new FormatInfo("heic", true, false),
new FormatInfo("hdr", true, true),
new FormatInfo("jpe", true, true),
new FormatInfo("dng", true, false),
new FormatInfo("mat", true, true),
new FormatInfo("pbm", true, true),
new FormatInfo("pfm", true, true),
new FormatInfo("pgm", true, true),
new FormatInfo("pnm", true, true),
new FormatInfo("ppm", true, true),
new FormatInfo("raw", false, true),
new FormatInfo("tif", true, true),
new FormatInfo("tiff", true, true),
new FormatInfo("jfif", true, true),
new FormatInfo("avif", true, true),
];
public readonly reportsProgress = false; public readonly reportsProgress = false;

View File

@ -32,10 +32,12 @@ class Files {
this.thumbnailQueue.add(async () => { this.thumbnailQueue.add(async () => {
const isAudio = converters const isAudio = converters
.find((c) => c.name === "ffmpeg") .find((c) => c.name === "ffmpeg")
?.supportedFormats?.includes(file.from.toLowerCase()); ?.formatStrings()
?.includes(file.from.toLowerCase());
const isVideo = converters const isVideo = converters
.find((c) => c.name === "vertd") .find((c) => c.name === "vertd")
?.supportedFormats?.includes(file.from.toLowerCase()); ?.formatStrings()
?.includes(file.from.toLowerCase());
try { try {
if (isAudio) { if (isAudio) {
@ -119,16 +121,16 @@ class Files {
return; return;
} }
const converter = converters.find((c) => const converter = converters.find((c) =>
c.supportedFormats.includes( c
format || ".somenonexistentextension", .formatStrings()
), .includes(format || ".somenonexistentextension"),
); );
if (!converter) { if (!converter) {
log(["files"], `no converter found for ${file.name}`); log(["files"], `no converter found for ${file.name}`);
this.files.push(new VertFile(file, format)); this.files.push(new VertFile(file, format));
return; return;
} }
const to = converter.supportedFormats.find((f) => f !== format); const to = converter.formatStrings().find((f) => f !== format);
if (!to) { if (!to) {
log(["files"], `no output format found for ${file.name}`); log(["files"], `no output format found for ${file.name}`);
return; return;

View File

@ -28,7 +28,7 @@ export class VertFile {
public findConverters(supportedFormats: string[] = [this.from]) { public findConverters(supportedFormats: string[] = [this.from]) {
const converter = this.converters.filter((converter) => const converter = this.converters.filter((converter) =>
converter.supportedFormats.map((f) => supportedFormats.includes(f)), converter.formatStrings().map((f) => supportedFormats.includes(f)),
); );
return converter; return converter;
} }
@ -36,8 +36,8 @@ export class VertFile {
public findConverter() { public findConverter() {
const converter = this.converters.find( const converter = this.converters.find(
(converter) => (converter) =>
converter.supportedFormats.includes(this.from) && converter.formatStrings().includes(this.from) &&
converter.supportedFormats.includes(this.to), converter.formatStrings().includes(this.to),
); );
return converter; return converter;
} }
@ -51,7 +51,7 @@ export class VertFile {
this.file = newFile; this.file = newFile;
this.to = to; this.to = to;
this.converters = converters.filter((c) => this.converters = converters.filter((c) =>
c.supportedFormats.includes(this.from), c.formatStrings().includes(this.from),
); );
this.convert = this.convert.bind(this); this.convert = this.convert.bind(this);
this.download = this.download.bind(this); this.download = this.download.bind(this);

View File

@ -8,8 +8,13 @@
const { data } = $props(); const { data } = $props();
const getSupportedFormats = (name: string) => const getSupportedFormats = (name: string) =>
converters.find((c) => c.name === name)?.supportedFormats.join(", ") || converters
"none"; .find((c) => c.name === name)
?.supportedFormats.map(
(f) =>
`${f.name}${f.fromSupported && f.toSupported ? "" : "*"}`,
)
.join(", ") || "none";
const status: { const status: {
[key: string]: { [key: string]: {

View File

@ -89,21 +89,25 @@
{@const availableConverters = file.findConverters()} {@const availableConverters = file.findConverters()}
{@const currentConverter = converters.find( {@const currentConverter = converters.find(
(c) => (c) =>
c.supportedFormats.includes(file.from) && c.formatStrings((f) => f.fromSupported).includes(file.from) &&
c.supportedFormats.includes(file.to), c.formatStrings((f) => f.toSupported).includes(file.to),
)} )}
{@const isAudio = converters {@const isAudio = converters
.find((c) => c.name === "ffmpeg") .find((c) => c.name === "ffmpeg")
?.supportedFormats.includes(file.from)} ?.formatStrings((f) => f.fromSupported)
.includes(file.from)}
{@const isVideo = converters {@const isVideo = converters
.find((c) => c.name === "vertd") .find((c) => c.name === "vertd")
?.supportedFormats.includes(file.from)} ?.formatStrings((f) => f.fromSupported)
.includes(file.from)}
{@const isImage = converters {@const isImage = converters
.find((c) => c.name === "libvips") .find((c) => c.name === "libvips")
?.supportedFormats.includes(file.from)} ?.formatStrings((f) => f.fromSupported)
.includes(file.from)}
{@const isDocument = converters {@const isDocument = converters
.find((c) => c.name === "pandoc") .find((c) => c.name === "pandoc")
?.supportedFormats.includes(file.from)} ?.formatStrings((f) => f.fromSupported)
.includes(file.from)}
<Panel class="p-5 flex flex-col min-w-0 gap-4 relative"> <Panel class="p-5 flex flex-col min-w-0 gap-4 relative">
<div class="flex-shrink-0 h-8 w-full flex items-center gap-2"> <div class="flex-shrink-0 h-8 w-full flex items-center gap-2">
{#if !converters.length} {#if !converters.length}
@ -222,7 +226,9 @@
<!-- cannot convert to svg or heif --> <!-- cannot convert to svg or heif -->
<Dropdown <Dropdown
options={availableConverters options={availableConverters
.flatMap((c) => c.supportedFormats) .flatMap((c) =>
c.formatStrings((f) => f.toSupported),
)
.filter( .filter(
(format) => (format) =>
format !== ".svg" && format !== ".heif", format !== ".svg" && format !== ".heif",