diff --git a/bun.lockb b/bun.lockb index 70a4bc8..ba0a4ef 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 2015120..2d9c4cb 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ "vite": "^5.0.3" }, "dependencies": { + "@ffmpeg/ffmpeg": "^0.12.10", + "@ffmpeg/util": "^0.12.1", "@fontsource/azeret-mono": "^5.1.0", "@fontsource/lexend": "^5.1.1", "@imagemagick/magick-wasm": "^0.0.31", diff --git a/src/lib/animation/index.ts b/src/lib/animation/index.ts index 6392734..6c8fed0 100644 --- a/src/lib/animation/index.ts +++ b/src/lib/animation/index.ts @@ -67,7 +67,6 @@ export const blur = ( ).matches; if (typeof config?.opacity === "undefined" && config) config.opacity = true; const isUsingTranslate = !!config?.x || !!config?.y || !!config?.scale; - console.log(isUsingTranslate); return { delay: config?.delay || 0, duration: prefersReducedMotion ? 0 : config?.duration || 300, @@ -155,7 +154,6 @@ export function flip( const [ox, oy] = style.transformOrigin.split(" ").map(parseFloat); const dx = from.left + (from.width * ox) / to.width - (to.left + ox); const dy = from.top + (from.height * oy) / to.height - (to.top + oy); - const { delay = 0, duration = (d) => Math.sqrt(d) * 120, diff --git a/src/lib/components/functional/FancyMenu.svelte b/src/lib/components/functional/FancyMenu.svelte new file mode 100644 index 0000000..bfff5e0 --- /dev/null +++ b/src/lib/components/functional/FancyMenu.svelte @@ -0,0 +1,76 @@ + + +
+ {#if activeLinkIndex !== -1} +
+ {/if} + {#each links as { name, url } (url)} + { + if (shouldGoBack) { + const currentIndex = links.findIndex((i) => + i.activeMatch($page.url.pathname), + ); + const nextIndex = links.findIndex((i) => + i.activeMatch(url), + ); + $shouldGoBack = nextIndex < currentIndex; + } + }} + > +
+ {#key name} + + {name} + + {/key} +
+
+ {/each} +
diff --git a/src/lib/converters/ffmpeg.svelte.ts b/src/lib/converters/ffmpeg.svelte.ts new file mode 100644 index 0000000..95cda30 --- /dev/null +++ b/src/lib/converters/ffmpeg.svelte.ts @@ -0,0 +1,61 @@ +import type { IFile } from "$lib/types"; +import { Converter } from "./converter.svelte"; +import type { OmitBetterStrict } from "$lib/types"; +import { FFmpeg } from "@ffmpeg/ffmpeg"; +import { browser } from "$app/environment"; + +export class FFmpegConverter extends Converter { + private ffmpeg: FFmpeg = null!; + public name = "ffmpeg"; + public ready = $state(false); + + public supportedFormats = [ + ".mp3", + ".wav", + ".flac", + ".ogg", + ".aac", + ".m4a", + ".opus", + ".wma", + ".m4a", + ".amr", + ".ac3", + "alac", + ".aiff", + ]; + + constructor() { + super(); + if (!browser) return; + this.ffmpeg = new FFmpeg(); + (async () => { + const baseURL = "https://unpkg.com/@ffmpeg/core@latest/dist/esm"; + await this.ffmpeg.load({ + coreURL: `${baseURL}/ffmpeg-core.js`, + wasmURL: `${baseURL}/ffmpeg-core.wasm`, + }); + + this.ready = true; + })(); + } + + public async convert( + input: OmitBetterStrict, + to: string, + ): Promise { + if (!to.startsWith(".")) to = `.${to}`; + // clone input.buffer + const buf = new Uint8Array(input.buffer); + await this.ffmpeg.writeFile("input", buf); + await this.ffmpeg.exec(["-i", "input", "output" + to]); + const output = (await this.ffmpeg.readFile( + "output" + to, + )) as unknown as Uint8Array; + return { + ...input, + buffer: output.buffer, + extension: to, + }; + } +} diff --git a/src/lib/converters/index.ts b/src/lib/converters/index.ts index d09bf00..9c412d2 100644 --- a/src/lib/converters/index.ts +++ b/src/lib/converters/index.ts @@ -1,3 +1,4 @@ +import { FFmpegConverter } from "./ffmpeg.svelte"; import { VipsConverter } from "./vips.svelte"; -export const converters = [new VipsConverter()]; +export const converters = [new VipsConverter(), new FFmpegConverter()]; diff --git a/src/lib/store/index.svelte.ts b/src/lib/store/index.svelte.ts index 134e96e..c837707 100644 --- a/src/lib/store/index.svelte.ts +++ b/src/lib/store/index.svelte.ts @@ -11,8 +11,6 @@ class Files { result?: (IFile & { blobUrl: string; animating: boolean }) | null; }[] >([]); - public conversionTypes = $state([]); - public conversionTypesReverse = $derived(this.conversionTypes.reverse()); public beenToConverterPage = $state(false); public shouldShowAlert = $derived( !this.beenToConverterPage && this.files.length > 0, diff --git a/src/lib/workers/magick.ts b/src/lib/workers/magick.ts index 9fb783c..2d568e0 100644 --- a/src/lib/workers/magick.ts +++ b/src/lib/workers/magick.ts @@ -6,8 +6,6 @@ import { } from "@imagemagick/magick-wasm"; import wasmUrl from "@imagemagick/magick-wasm/magick.wasm?url"; -console.log(wasmUrl); - const magickPromise = fetch(wasmUrl) .then((r) => r.arrayBuffer()) .then((r) => initializeImageMagick(r)); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 32ffd0b..dc85a70 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,28 +5,40 @@ import { quintOut } from "svelte/easing"; import { files } from "$lib/store/index.svelte"; import Logo from "$lib/components/visual/svg/Logo.svelte"; - import { fly } from "svelte/transition"; import featuredImage from "$lib/assets/VERT_Feature.webp"; import { PUB_HOSTNAME, PUB_PLAUSIBLE_URL } from "$env/static/public"; + import FancyMenu from "$lib/components/functional/FancyMenu.svelte"; + import { writable } from "svelte/store"; let { children, data } = $props(); - let navWidth = $state(1); - let shouldGoBack = $state(false); + let shouldGoBack = writable(false); - const links = $derived<{ - [key: string]: string; - }>({ - Upload: "/", - [files.files.length > 0 - ? `Convert (${files.files.length})` - : `Convert`]: "/convert", - About: "/about", - }); - - const linkCount = $derived(Object.keys(links).length); - const linkIndex = $derived( - Object.keys(links).findIndex((link) => links[link] === data.pathname), - ); + const links = $derived< + { + name: string; + url: string; + activeMatch: (pathname: string) => boolean; + }[] + >([ + { + name: "Upload", + url: "/", + activeMatch: (pathname) => pathname === "/", + }, + { + name: + files.files.length > 0 + ? `Convert (${files.files.length})` + : `Convert`, + url: "/convert", + activeMatch: (pathname) => pathname === "/convert", + }, + { + name: "About", + url: "/about", + activeMatch: (pathname) => pathname.startsWith("/about"), + }, + ]); const maybeNavToHome = (e: DragEvent) => { if (e.dataTransfer?.types.includes("Files")) { @@ -78,54 +90,7 @@ - +
{#key data.pathname} @@ -137,7 +102,7 @@ easing: quintOut, blurMultiplier: 12, x: { - start: !shouldGoBack ? 250 : -250, + start: !$shouldGoBack ? 250 : -250, end: 0, }, scale: { @@ -151,7 +116,7 @@ blurMultiplier: 12, x: { start: 0, - end: !shouldGoBack ? -250 : 250, + end: !$shouldGoBack ? -250 : 250, }, scale: { start: 1, diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 14297d5..cfce393 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,13 +12,22 @@ const runUpload = () => { files.files = [ ...files.files, - ...(ourFiles || []).map((f) => ({ - file: f, - from: "." + f.name.split(".").slice(-1), - to: converters[0].supportedFormats[0], - blobUrl: URL.createObjectURL(f), - id: Math.random().toString(36).substring(2), - })), + ...(ourFiles || []).map((f, i) => { + const from = "." + f.name.toLowerCase().split(".").slice(-1); + const converter = converters.find((c) => + c.supportedFormats.includes(from), + ); + const to = + converter?.supportedFormats.find((f) => f !== from) || + converters[0].supportedFormats[0]; + return { + file: f, + from, + to, + blobUrl: URL.createObjectURL(f), + id: Math.random().toString(36).substring(2), + }; + }), ]; ourFiles = []; diff --git a/src/routes/convert/+page.svelte b/src/routes/convert/+page.svelte index 81f17f4..29fd5e4 100644 --- a/src/routes/convert/+page.svelte +++ b/src/routes/convert/+page.svelte @@ -1,15 +1,17 @@