mirror of https://github.com/VERT-sh/VERT.git
Merge branch 'merged-video-audio-and-dropdowns' into feat/merge-big-stuff
This commit is contained in:
commit
d543433007
|
@ -62,35 +62,79 @@
|
||||||
categories[cat].canConvertTo?.includes(currentCategory || ""),
|
categories[cat].canConvertTo?.includes(currentCategory || ""),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shouldInclude = (format: string, category: string): boolean => {
|
||||||
|
// if converting from audio to video, dont show gifs
|
||||||
|
if (categories["audio"]?.formats.includes(from ?? "") && format === ".gif") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const filteredData = $derived.by(() => {
|
const filteredData = $derived.by(() => {
|
||||||
|
const normalize = (str: string) => str.replace(/^\./, "").toLowerCase();
|
||||||
|
|
||||||
|
// if no query, return formats for current category
|
||||||
if (!searchQuery) {
|
if (!searchQuery) {
|
||||||
return {
|
return {
|
||||||
categories: availableCategories,
|
categories: availableCategories,
|
||||||
formats: currentCategory
|
formats: currentCategory
|
||||||
? categories[currentCategory].formats
|
? categories[currentCategory].formats.filter((format) =>
|
||||||
|
shouldInclude(format, currentCategory!),
|
||||||
|
)
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const searchLower = normalize(searchQuery);
|
||||||
|
|
||||||
// filter categories that have matching formats
|
// find all categories that have formats matching the search query
|
||||||
const matchingCategories = availableCategories.filter((cat) =>
|
const matchingCategories = availableCategories.filter((cat) =>
|
||||||
categories[cat].formats.some((format) =>
|
categories[cat].formats.some(
|
||||||
format.toLowerCase().includes(searchQuery.toLowerCase()),
|
(format) =>
|
||||||
|
normalize(format).includes(searchLower) &&
|
||||||
|
shouldInclude(format, cat),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (matchingCategories.length === 0) {
|
||||||
|
return {
|
||||||
|
categories: availableCategories,
|
||||||
|
formats: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// only show formats from the current category that match the search
|
// if current category has no matches, switch to first category that does
|
||||||
const filteredFormats =
|
const currentCategoryHasMatches =
|
||||||
currentCategory && categories[currentCategory]
|
currentCategory &&
|
||||||
? categories[currentCategory].formats.filter((format) =>
|
matchingCategories.some((cat) => cat === currentCategory);
|
||||||
format
|
if (!currentCategoryHasMatches && matchingCategories.length > 0) {
|
||||||
.toLowerCase()
|
const newCategory = matchingCategories[0];
|
||||||
.includes(searchQuery.toLowerCase()),
|
currentCategory = newCategory;
|
||||||
)
|
}
|
||||||
: [];
|
|
||||||
|
// return formats only from the current category that match the search
|
||||||
|
let filteredFormats = currentCategory
|
||||||
|
? categories[currentCategory].formats.filter(
|
||||||
|
(format) =>
|
||||||
|
normalize(format).includes(searchLower) &&
|
||||||
|
shouldInclude(format, currentCategory!),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// sorting exact match first, then others
|
||||||
|
filteredFormats = filteredFormats.sort((a, b) => {
|
||||||
|
const aExact = normalize(a) === searchLower;
|
||||||
|
const bExact = normalize(b) === searchLower;
|
||||||
|
if (aExact && !bExact) return -1;
|
||||||
|
if (!aExact && bExact) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
categories: matchingCategories,
|
categories:
|
||||||
|
matchingCategories.length > 0
|
||||||
|
? matchingCategories
|
||||||
|
: availableCategories,
|
||||||
formats: filteredFormats,
|
formats: filteredFormats,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -98,6 +142,21 @@
|
||||||
const selectOption = (option: string) => {
|
const selectOption = (option: string) => {
|
||||||
selected = option;
|
selected = option;
|
||||||
open = false;
|
open = false;
|
||||||
|
|
||||||
|
// find the category of this option if it's not in the current category
|
||||||
|
if (
|
||||||
|
currentCategory &&
|
||||||
|
!categories[currentCategory].formats.includes(option)
|
||||||
|
) {
|
||||||
|
const formatCategory = Object.keys(categories).find((cat) =>
|
||||||
|
categories[cat].formats.includes(option),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (formatCategory) {
|
||||||
|
currentCategory = formatCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onselect?.(option);
|
onselect?.(option);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,7 +166,39 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (event: Event) => {
|
const handleSearch = (event: Event) => {
|
||||||
searchQuery = (event.target as HTMLInputElement).value;
|
const query = (event.target as HTMLInputElement).value;
|
||||||
|
searchQuery = query;
|
||||||
|
|
||||||
|
// find which categories have matching formats & switch
|
||||||
|
if (query) {
|
||||||
|
const queryLower = query.toLowerCase();
|
||||||
|
const categoriesWithMatches = availableCategories.filter((cat) =>
|
||||||
|
categories[cat].formats.some((format) =>
|
||||||
|
format.toLowerCase().includes(queryLower),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (categoriesWithMatches.length > 0) {
|
||||||
|
const currentHasMatches =
|
||||||
|
currentCategory &&
|
||||||
|
categories[currentCategory].formats.some((format) =>
|
||||||
|
format.toLowerCase().includes(queryLower),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentHasMatches) {
|
||||||
|
currentCategory = categoriesWithMatches[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEnter = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (filteredData.formats.length > 0) {
|
||||||
|
selectOption(filteredData.formats[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickDropdown = () => {
|
const clickDropdown = () => {
|
||||||
|
@ -201,7 +292,6 @@
|
||||||
{#if open}
|
{#if open}
|
||||||
<div
|
<div
|
||||||
bind:this={dropdownMenu}
|
bind:this={dropdownMenu}
|
||||||
style={hover ? "will-change: opacity, fade, transform" : ""}
|
|
||||||
transition:fade={{
|
transition:fade={{
|
||||||
duration,
|
duration,
|
||||||
easing: quintOut,
|
easing: quintOut,
|
||||||
|
@ -219,6 +309,7 @@
|
||||||
class="flex-grow w-full !pl-11 !pr-3 rounded-lg bg-panel text-foreground"
|
class="flex-grow w-full !pl-11 !pr-3 rounded-lg bg-panel text-foreground"
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
oninput={handleSearch}
|
oninput={handleSearch}
|
||||||
|
onkeydown={onEnter}
|
||||||
onfocus={() => {}}
|
onfocus={() => {}}
|
||||||
id="format-search"
|
id="format-search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
@ -228,15 +319,25 @@
|
||||||
>
|
>
|
||||||
<SearchIcon class="w-4 h-4" />
|
<SearchIcon class="w-4 h-4" />
|
||||||
</span>
|
</span>
|
||||||
|
{#if searchQuery}
|
||||||
|
<span
|
||||||
|
class="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-muted"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
>
|
||||||
|
{filteredData.formats.length}
|
||||||
|
{filteredData.formats.length === 1
|
||||||
|
? "result"
|
||||||
|
: "results"}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- available categories -->
|
<!-- available categories -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
{#each filteredData.categories as category}
|
{#each filteredData.categories as category}
|
||||||
<button
|
<button
|
||||||
class="flex-grow text-lg hover:text-muted/20 border-b-[1px] pb-2 capitalize {currentCategory ===
|
class="flex-grow text-lg hover:text-muted/20 border-b-[1px] pb-2 capitalize
|
||||||
category
|
{currentCategory === category
|
||||||
? 'text-accent border-b-accent'
|
? 'text-accent border-b-accent'
|
||||||
: 'border-b-separator text-muted'}"
|
: 'border-b-separator text-muted'}"
|
||||||
onclick={() => selectCategory(category)}
|
onclick={() => selectCategory(category)}
|
||||||
|
@ -245,21 +346,26 @@
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- available formats -->
|
<!-- available formats -->
|
||||||
<div class="max-h-80 overflow-y-auto grid grid-cols-3 gap-2 p-2">
|
<div class="max-h-80 overflow-y-auto grid grid-cols-3 gap-2 p-2">
|
||||||
{#each filteredData.formats as format}
|
{#if filteredData.formats.length > 0}
|
||||||
<button
|
{#each filteredData.formats as format}
|
||||||
class="w-full p-2 text-center rounded-xl
|
<button
|
||||||
{format === selected
|
class="w-full p-2 text-center rounded-xl
|
||||||
? 'bg-accent text-black'
|
{format === selected ? 'bg-accent text-black' : 'hover:bg-panel'}
|
||||||
: 'hover:bg-panel'}
|
|
||||||
{format === from ? 'bg-separator' : ''}"
|
{format === from ? 'bg-separator' : ''}"
|
||||||
onclick={() => selectOption(format)}
|
onclick={() => selectOption(format)}
|
||||||
>
|
>
|
||||||
{format}
|
{format}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div class="col-span-3 text-center p-4 text-muted">
|
||||||
|
{searchQuery
|
||||||
|
? "No formats match your search"
|
||||||
|
: "No formats available"}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -5,8 +5,9 @@ export class FormatInfo {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
name: string,
|
name: string,
|
||||||
public fromSupported: boolean,
|
public fromSupported = true,
|
||||||
public toSupported: boolean,
|
public toSupported = true,
|
||||||
|
public isNative = true,
|
||||||
) {
|
) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
if (!this.name.startsWith(".")) {
|
if (!this.name.startsWith(".")) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { error, log } from "$lib/logger";
|
||||||
import { addToast } from "$lib/store/ToastProvider";
|
import { addToast } from "$lib/store/ToastProvider";
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
|
|
||||||
|
const videoFormats = [".mkv", ".mp4", ".avi", ".mov", ".webm", ".ts", ".mts", ".m2ts", ".wmv"];
|
||||||
|
|
||||||
export class FFmpegConverter extends Converter {
|
export class FFmpegConverter extends Converter {
|
||||||
private ffmpeg: FFmpeg = null!;
|
private ffmpeg: FFmpeg = null!;
|
||||||
public name = "ffmpeg";
|
public name = "ffmpeg";
|
||||||
|
@ -23,9 +25,10 @@ export class FFmpegConverter extends Converter {
|
||||||
new FormatInfo("wma", true, true),
|
new FormatInfo("wma", true, true),
|
||||||
new FormatInfo("amr", true, true),
|
new FormatInfo("amr", true, true),
|
||||||
new FormatInfo("ac3", true, true),
|
new FormatInfo("ac3", true, true),
|
||||||
new FormatInfo("alac", true, false),
|
new FormatInfo("alac", true, true),
|
||||||
new FormatInfo("aiff", true, true),
|
new FormatInfo("aiff", true, true),
|
||||||
new FormatInfo("aif", true, true),
|
new FormatInfo("aif", true, true),
|
||||||
|
...videoFormats.map((f) => new FormatInfo(f, true, true, false)),
|
||||||
];
|
];
|
||||||
|
|
||||||
public readonly reportsProgress = true;
|
public readonly reportsProgress = true;
|
||||||
|
@ -61,6 +64,17 @@ export class FFmpegConverter extends Converter {
|
||||||
ffmpeg.on("progress", (progress) => {
|
ffmpeg.on("progress", (progress) => {
|
||||||
input.progress = progress.progress * 100;
|
input.progress = progress.progress * 100;
|
||||||
});
|
});
|
||||||
|
ffmpeg.on("log", (l) => {
|
||||||
|
log(["converters", this.name], l.message);
|
||||||
|
|
||||||
|
if (l.message.includes("Stream map '0:a:0' matches no streams.")) {
|
||||||
|
error(
|
||||||
|
["converters", this.name],
|
||||||
|
`No audio stream found in ${input.name}.`,
|
||||||
|
);
|
||||||
|
addToast("error", `No audio stream found in ${input.name}.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
const baseURL =
|
const baseURL =
|
||||||
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm";
|
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm";
|
||||||
await ffmpeg.load({
|
await ffmpeg.load({
|
||||||
|
@ -73,7 +87,37 @@ export class FFmpegConverter extends Converter {
|
||||||
["converters", this.name],
|
["converters", this.name],
|
||||||
`wrote ${input.name} to ffmpeg virtual fs`,
|
`wrote ${input.name} to ffmpeg virtual fs`,
|
||||||
);
|
);
|
||||||
await ffmpeg.exec(["-i", "input", "output" + to]);
|
if (videoFormats.includes(input.from.slice(1))) {
|
||||||
|
// create an audio track from the video
|
||||||
|
await ffmpeg.exec(["-i", "input", "-map", "0:a:0", "output" + to]);
|
||||||
|
} else if (videoFormats.includes(to.slice(1))) {
|
||||||
|
// nab the album art
|
||||||
|
await ffmpeg.exec([
|
||||||
|
"-i",
|
||||||
|
"input",
|
||||||
|
"-an",
|
||||||
|
"-vcodec",
|
||||||
|
"copy",
|
||||||
|
"cover.png",
|
||||||
|
]);
|
||||||
|
const cmd = [
|
||||||
|
"-i",
|
||||||
|
"input",
|
||||||
|
"-i",
|
||||||
|
"cover.png",
|
||||||
|
"-loop",
|
||||||
|
"1",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
...toArgs(to),
|
||||||
|
"output" + to,
|
||||||
|
];
|
||||||
|
console.log(cmd);
|
||||||
|
await ffmpeg.exec(cmd);
|
||||||
|
} else {
|
||||||
|
await ffmpeg.exec(["-i", "input", "output" + to]);
|
||||||
|
}
|
||||||
|
|
||||||
log(["converters", this.name], `executed ffmpeg command`);
|
log(["converters", this.name], `executed ffmpeg command`);
|
||||||
const output = (await ffmpeg.readFile(
|
const output = (await ffmpeg.readFile(
|
||||||
"output" + to,
|
"output" + to,
|
||||||
|
@ -86,3 +130,49 @@ export class FFmpegConverter extends Converter {
|
||||||
return new VertFile(new File([output], input.name), to);
|
return new VertFile(new File([output], input.name), to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// and here i was, thinking i'd be done with ffmpeg after finishing vertd
|
||||||
|
// but OH NO we just HAD to have someone suggest to allow album art video generation.
|
||||||
|
//
|
||||||
|
// i hate you SO much.
|
||||||
|
// - love, maddie
|
||||||
|
const toArgs = (ext: string): string[] => {
|
||||||
|
const encoder = getEncoder(ext);
|
||||||
|
const args = ["-c:v", encoder];
|
||||||
|
switch (encoder) {
|
||||||
|
case "libx264": {
|
||||||
|
args.push(
|
||||||
|
"-preset",
|
||||||
|
"ultrafast",
|
||||||
|
"-crf",
|
||||||
|
"18",
|
||||||
|
"-tune",
|
||||||
|
"stillimage",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "libvpx": {
|
||||||
|
args.push("-c:v", "libvpx-vp9", "-c:a", "libvorbis");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEncoder = (ext: string): string => {
|
||||||
|
switch (ext) {
|
||||||
|
case ".mkv":
|
||||||
|
case ".mp4":
|
||||||
|
case ".avi":
|
||||||
|
case ".mov":
|
||||||
|
return "libx264";
|
||||||
|
case ".webm":
|
||||||
|
return "libvpx";
|
||||||
|
default:
|
||||||
|
return "copy";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Categories } from "$lib/types";
|
import type { Categories } from "$lib/types";
|
||||||
|
import type { Converter } from "./converter.svelte";
|
||||||
import { FFmpegConverter } from "./ffmpeg.svelte";
|
import { FFmpegConverter } from "./ffmpeg.svelte";
|
||||||
import { PandocConverter } from "./pandoc.svelte";
|
import { PandocConverter } from "./pandoc.svelte";
|
||||||
import { VertdConverter } from "./vertd.svelte";
|
import { VertdConverter } from "./vertd.svelte";
|
||||||
|
@ -22,19 +23,21 @@ export function getConverterByFormat(format: string) {
|
||||||
|
|
||||||
export const categories: Categories = {
|
export const categories: Categories = {
|
||||||
image: { formats: [""], canConvertTo: [] },
|
image: { formats: [""], canConvertTo: [] },
|
||||||
video: { formats: [""], canConvertTo: [] }, // add "audio" when "nullptr/experimental-audio-to-video" is implemented
|
video: { formats: [""], canConvertTo: ["audio"] },
|
||||||
audio: { formats: [""], canConvertTo: [] }, // add "video" when "nullptr/experimental-audio-to-video" is implemented
|
audio: { formats: [""], canConvertTo: ["video"] },
|
||||||
docs: { formats: [""], canConvertTo: [] },
|
docs: { formats: [""], canConvertTo: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
categories.audio.formats =
|
categories.audio.formats =
|
||||||
converters
|
converters
|
||||||
.find((c) => c.name === "ffmpeg")
|
.find((c) => c.name === "ffmpeg")
|
||||||
?.formatStrings((f) => f.toSupported) || [];
|
?.supportedFormats.filter((f) => f.toSupported && f.isNative)
|
||||||
|
.map((f) => f.name) || [];
|
||||||
categories.video.formats =
|
categories.video.formats =
|
||||||
converters
|
converters
|
||||||
.find((c) => c.name === "vertd")
|
.find((c) => c.name === "vertd")
|
||||||
?.formatStrings((f) => f.toSupported) || [];
|
?.supportedFormats.filter((f) => f.toSupported && f.isNative)
|
||||||
|
.map((f) => f.name) || [];
|
||||||
categories.image.formats =
|
categories.image.formats =
|
||||||
converters
|
converters
|
||||||
.find((c) => c.name === "imagemagick")
|
.find((c) => c.name === "imagemagick")
|
||||||
|
@ -42,4 +45,18 @@ categories.image.formats =
|
||||||
categories.docs.formats =
|
categories.docs.formats =
|
||||||
converters
|
converters
|
||||||
.find((c) => c.name === "pandoc")
|
.find((c) => c.name === "pandoc")
|
||||||
?.formatStrings((f) => f.toSupported) || [];
|
?.supportedFormats.filter((f) => f.toSupported && f.isNative)
|
||||||
|
.map((f) => f.name) || [];
|
||||||
|
|
||||||
|
|
||||||
|
export const byNative = (format: string) => {
|
||||||
|
return (a: Converter, b: Converter) => {
|
||||||
|
const aFormat = a.supportedFormats.find((f) => f.name === format);
|
||||||
|
const bFormat = b.supportedFormats.find((f) => f.name === format);
|
||||||
|
|
||||||
|
if (aFormat && bFormat) {
|
||||||
|
return aFormat.isNative ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -38,8 +38,8 @@ export class MagickConverter extends Converter {
|
||||||
new FormatInfo("icns", true, false),
|
new FormatInfo("icns", true, false),
|
||||||
new FormatInfo("nef", true, false),
|
new FormatInfo("nef", true, false),
|
||||||
new FormatInfo("cr2", true, false),
|
new FormatInfo("cr2", true, false),
|
||||||
new FormatInfo("hdr", true, true),
|
new FormatInfo("hdr"),
|
||||||
new FormatInfo("jpe", true, true),
|
new FormatInfo("jpe"),
|
||||||
new FormatInfo("dng", true, false),
|
new FormatInfo("dng", true, false),
|
||||||
new FormatInfo("mat", true, true),
|
new FormatInfo("mat", true, true),
|
||||||
new FormatInfo("pbm", true, true),
|
new FormatInfo("pbm", true, true),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import { converters } from "$lib/converters";
|
import { byNative, converters } from "$lib/converters";
|
||||||
import { error, log } from "$lib/logger";
|
import { error, log } from "$lib/logger";
|
||||||
import { VertFile } from "$lib/types";
|
import { VertFile } from "$lib/types";
|
||||||
import { parseBlob, selectCover } from "music-metadata";
|
import { parseBlob, selectCover } from "music-metadata";
|
||||||
|
@ -33,11 +33,13 @@ 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")
|
||||||
?.formatStrings()
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
?.includes(file.from.toLowerCase());
|
?.includes(file.from.toLowerCase());
|
||||||
const isVideo = converters
|
const isVideo = converters
|
||||||
.find((c) => c.name === "vertd")
|
.find((c) => c.name === "vertd")
|
||||||
?.formatStrings()
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
?.includes(file.from.toLowerCase());
|
?.includes(file.from.toLowerCase());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -121,11 +123,11 @@ class Files {
|
||||||
log(["files"], `no extension found for ${file.name}`);
|
log(["files"], `no extension found for ${file.name}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const converter = converters.find((c) =>
|
const converter = converters
|
||||||
c
|
.sort(byNative(format))
|
||||||
.formatStrings()
|
.find((converter) =>
|
||||||
.includes(format || ".somenonexistentextension"),
|
converter.formatStrings().includes(format),
|
||||||
);
|
);
|
||||||
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));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { converters } from "$lib/converters";
|
import { byNative, converters } from "$lib/converters";
|
||||||
import type { Converter } from "$lib/converters/converter.svelte";
|
import type { Converter } from "$lib/converters/converter.svelte";
|
||||||
import { error } from "$lib/logger";
|
import { error } from "$lib/logger";
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
|
@ -28,18 +28,35 @@ export class VertFile {
|
||||||
public converters: Converter[] = [];
|
public converters: Converter[] = [];
|
||||||
|
|
||||||
public findConverters(supportedFormats: string[] = [this.from]) {
|
public findConverters(supportedFormats: string[] = [this.from]) {
|
||||||
const converter = this.converters.filter((converter) =>
|
const converter = this.converters
|
||||||
converter.formatStrings().map((f) => supportedFormats.includes(f)),
|
.filter((converter) =>
|
||||||
);
|
converter
|
||||||
|
.formatStrings()
|
||||||
|
.map((f) => supportedFormats.includes(f)),
|
||||||
|
)
|
||||||
|
.sort(byNative(this.from));
|
||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public findConverter() {
|
public findConverter() {
|
||||||
const converter = this.converters.find(
|
const converter = this.converters.find((converter) => {
|
||||||
(converter) =>
|
if (
|
||||||
converter.formatStrings().includes(this.from) &&
|
!converter.formatStrings().includes(this.from) ||
|
||||||
converter.formatStrings().includes(this.to),
|
!converter.formatStrings().includes(this.to)
|
||||||
);
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theirFrom = converter.supportedFormats.find(
|
||||||
|
(f) => f.name === this.from,
|
||||||
|
);
|
||||||
|
const theirTo = converter.supportedFormats.find(
|
||||||
|
(f) => f.name === this.to,
|
||||||
|
);
|
||||||
|
if (!theirFrom || !theirTo) return false;
|
||||||
|
if (!theirFrom.isNative && !theirTo.isNative) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import Panel from "$lib/components/visual/Panel.svelte";
|
import Panel from "$lib/components/visual/Panel.svelte";
|
||||||
import ProgressBar from "$lib/components/visual/ProgressBar.svelte";
|
import ProgressBar from "$lib/components/visual/ProgressBar.svelte";
|
||||||
import Tooltip from "$lib/components/visual/Tooltip.svelte";
|
import Tooltip from "$lib/components/visual/Tooltip.svelte";
|
||||||
import { categories, converters } from "$lib/converters";
|
import { categories, converters, byNative } from "$lib/converters";
|
||||||
import {
|
import {
|
||||||
effects,
|
effects,
|
||||||
files,
|
files,
|
||||||
|
@ -52,21 +52,30 @@
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Set gradient color depending on the file types
|
// Set gradient color depending on the file types
|
||||||
// TODO: if more file types added, add a "fileType" property to the file object
|
// TODO: if more file types added, add a "fileType" property to the file object
|
||||||
const allAudio = files.files.every(
|
const allAudio = files.files.every((file) => {
|
||||||
(file) => file.findConverter()?.name === "ffmpeg",
|
const converter = file
|
||||||
);
|
.findConverters()
|
||||||
const allImages = files.files.every(
|
.sort(byNative(file.from))[0];
|
||||||
(file) =>
|
return converter?.name === "ffmpeg";
|
||||||
file.findConverter()?.name !== "ffmpeg" &&
|
});
|
||||||
file.findConverter()?.name !== "vertd",
|
const allImages = files.files.every((file) => {
|
||||||
);
|
const converter = file
|
||||||
const allVideos = files.files.every(
|
.findConverters()
|
||||||
(file) => file.findConverter()?.name === "vertd",
|
.sort(byNative(file.from))[0];
|
||||||
);
|
return converter?.name === "libvips";
|
||||||
|
});
|
||||||
const allDocuments = files.files.every(
|
const allVideos = files.files.every((file) => {
|
||||||
(file) => file.findConverter()?.name === "pandoc",
|
const converter = file
|
||||||
);
|
.findConverters()
|
||||||
|
.sort(byNative(file.from))[0];
|
||||||
|
return converter?.name === "vertd";
|
||||||
|
});
|
||||||
|
const allDocuments = files.files.every((file) => {
|
||||||
|
const converter = file
|
||||||
|
.findConverters()
|
||||||
|
.sort(byNative(file.from))[0];
|
||||||
|
return converter?.name === "pandoc";
|
||||||
|
});
|
||||||
|
|
||||||
if (files.files.length === 1 && files.files[0].blobUrl && !allVideos) {
|
if (files.files.length === 1 && files.files[0].blobUrl && !allVideos) {
|
||||||
showGradient.set(false);
|
showGradient.set(false);
|
||||||
|
@ -76,7 +85,7 @@
|
||||||
|
|
||||||
if (
|
if (
|
||||||
files.files.length === 0 ||
|
files.files.length === 0 ||
|
||||||
(!allAudio && !allImages && !allVideos)
|
(!allAudio && !allImages && !allVideos && !allDocuments)
|
||||||
) {
|
) {
|
||||||
gradientColor.set("");
|
gradientColor.set("");
|
||||||
} else {
|
} else {
|
||||||
|
@ -96,7 +105,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet fileItem(file: VertFile, index: number)}
|
{#snippet fileItem(file: VertFile, index: number)}
|
||||||
{@const availableConverters = file.findConverters()}
|
|
||||||
{@const currentConverter = converters.find(
|
{@const currentConverter = converters.find(
|
||||||
(c) =>
|
(c) =>
|
||||||
c.formatStrings((f) => f.fromSupported).includes(file.from) &&
|
c.formatStrings((f) => f.fromSupported).includes(file.from) &&
|
||||||
|
@ -104,11 +112,13 @@
|
||||||
)}
|
)}
|
||||||
{@const isAudio = converters
|
{@const isAudio = converters
|
||||||
.find((c) => c.name === "ffmpeg")
|
.find((c) => c.name === "ffmpeg")
|
||||||
?.formatStrings((f) => f.fromSupported)
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
.includes(file.from)}
|
.includes(file.from)}
|
||||||
{@const isVideo = converters
|
{@const isVideo = converters
|
||||||
.find((c) => c.name === "vertd")
|
.find((c) => c.name === "vertd")
|
||||||
?.formatStrings((f) => f.fromSupported)
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
.includes(file.from)}
|
.includes(file.from)}
|
||||||
{@const isImage = converters
|
{@const isImage = converters
|
||||||
.find((c) => c.name === "imagemagick")
|
.find((c) => c.name === "imagemagick")
|
||||||
|
@ -116,7 +126,8 @@
|
||||||
.includes(file.from)}
|
.includes(file.from)}
|
||||||
{@const isDocument = converters
|
{@const isDocument = converters
|
||||||
.find((c) => c.name === "pandoc")
|
.find((c) => c.name === "pandoc")
|
||||||
?.formatStrings((f) => f.fromSupported)
|
?.supportedFormats.filter((f) => f.isNative)
|
||||||
|
.map((f) => f.name)
|
||||||
.includes(file.from)}
|
.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">
|
||||||
|
|
Loading…
Reference in New Issue