feat: Expand supported image and audio formats, update UI color palette, and refresh application assets.

This commit is contained in:
2026-01-30 14:58:14 +01:00
parent 95ee627f1d
commit 76198f28c5
10 changed files with 162 additions and 42 deletions

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/png" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>K-Convert - Privacy-First File Converter</title> <title>K-Convert - Privacy-First File Converter</title>
</head> </head>

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -27,7 +27,10 @@ function getWorker(): WorkerType {
return workerInstance; return workerInstance;
} }
const SUPPORTED_FORMATS: AudioFormat[] = ['mp3', 'wav', 'ogg', 'm4a', 'flac']; const SUPPORTED_FORMATS: AudioFormat[] = [
'mp3', 'wav', 'ogg', 'm4a', 'flac',
'aac', 'aiff', 'alac', 'wma', 'opus', 'm4r', 'amr'
];
export class FfmpegAdapter implements IFileConverter { export class FfmpegAdapter implements IFileConverter {
readonly name = 'ffmpeg-adapter'; readonly name = 'ffmpeg-adapter';

View File

@@ -17,8 +17,11 @@ import type { ImageConverterWorker } from '../workers/image.worker';
// Formats heic2any can directly output // Formats heic2any can directly output
const HEIC2ANY_OUTPUTS: ImageFormat[] = ['jpeg', 'png']; const HEIC2ANY_OUTPUTS: ImageFormat[] = ['jpeg', 'png'];
// All image formats we support as final output // All image formats we support as final output (via two-step conversion)
const ALL_IMAGE_OUTPUTS: ImageFormat[] = ['jpeg', 'png', 'webp', 'gif', 'bmp', 'tiff', 'ico']; const ALL_IMAGE_OUTPUTS: ImageFormat[] = [
'jpeg', 'png', 'webp', 'gif', 'bmp', 'tiff', 'ico',
'avif', 'jxl', 'svg', 'psd', 'tga'
];
type WorkerType = Comlink.Remote<ImageConverterWorker>; type WorkerType = Comlink.Remote<ImageConverterWorker>;
let workerInstance: WorkerType | null = null; let workerInstance: WorkerType | null = null;

View File

@@ -27,8 +27,11 @@ function getWorker(): WorkerType {
return workerInstance; return workerInstance;
} }
// Formats that ImageMagick can handle // Formats that ImageMagick can handle (excludes HEIC which uses heic-adapter)
const SUPPORTED_FORMATS: ImageFormat[] = ['jpeg', 'png', 'webp', 'gif', 'bmp', 'tiff', 'ico']; const SUPPORTED_FORMATS: ImageFormat[] = [
'jpeg', 'png', 'webp', 'gif', 'bmp', 'tiff', 'ico',
'avif', 'jxl', 'svg', 'psd', 'raw', 'tga'
];
export class ImageMagickAdapter implements IFileConverter { export class ImageMagickAdapter implements IFileConverter {
readonly name = 'imagemagick-adapter'; readonly name = 'imagemagick-adapter';

View File

@@ -6,8 +6,16 @@
// Format Definitions // Format Definitions
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export type ImageFormat = 'jpeg' | 'png' | 'webp' | 'gif' | 'bmp' | 'tiff' | 'heic' | 'ico'; // Image formats
export type AudioFormat = 'mp3' | 'wav' | 'ogg' | 'm4a' | 'flac'; export type ImageFormat =
| 'jpeg' | 'png' | 'webp' | 'gif' | 'bmp' | 'tiff' | 'heic' | 'ico'
| 'avif' | 'jxl' | 'svg' | 'psd' | 'raw' | 'tga';
// Audio formats
export type AudioFormat =
| 'mp3' | 'wav' | 'ogg' | 'm4a' | 'flac'
| 'aac' | 'aiff' | 'alac' | 'wma' | 'opus' | 'm4r' | 'amr';
export type SupportedFormat = ImageFormat | AudioFormat; export type SupportedFormat = ImageFormat | AudioFormat;
export type FormatCategory = 'image' | 'audio'; export type FormatCategory = 'image' | 'audio';
@@ -20,7 +28,7 @@ export interface FormatMetadata {
} }
export const FORMAT_METADATA: Record<SupportedFormat, FormatMetadata> = { export const FORMAT_METADATA: Record<SupportedFormat, FormatMetadata> = {
// Images // Images - Common
jpeg: { extension: 'jpg', mimeType: 'image/jpeg', displayName: 'JPEG', category: 'image' }, jpeg: { extension: 'jpg', mimeType: 'image/jpeg', displayName: 'JPEG', category: 'image' },
png: { extension: 'png', mimeType: 'image/png', displayName: 'PNG', category: 'image' }, png: { extension: 'png', mimeType: 'image/png', displayName: 'PNG', category: 'image' },
webp: { extension: 'webp', mimeType: 'image/webp', displayName: 'WebP', category: 'image' }, webp: { extension: 'webp', mimeType: 'image/webp', displayName: 'WebP', category: 'image' },
@@ -29,16 +37,32 @@ export const FORMAT_METADATA: Record<SupportedFormat, FormatMetadata> = {
tiff: { extension: 'tiff', mimeType: 'image/tiff', displayName: 'TIFF', category: 'image' }, tiff: { extension: 'tiff', mimeType: 'image/tiff', displayName: 'TIFF', category: 'image' },
heic: { extension: 'heic', mimeType: 'image/heic', displayName: 'HEIC', category: 'image' }, heic: { extension: 'heic', mimeType: 'image/heic', displayName: 'HEIC', category: 'image' },
ico: { extension: 'ico', mimeType: 'image/x-icon', displayName: 'ICO', category: 'image' }, ico: { extension: 'ico', mimeType: 'image/x-icon', displayName: 'ICO', category: 'image' },
// Audio // Images - New formats
avif: { extension: 'avif', mimeType: 'image/avif', displayName: 'AVIF', category: 'image' },
jxl: { extension: 'jxl', mimeType: 'image/jxl', displayName: 'JPEG XL', category: 'image' },
svg: { extension: 'svg', mimeType: 'image/svg+xml', displayName: 'SVG', category: 'image' },
psd: { extension: 'psd', mimeType: 'image/vnd.adobe.photoshop', displayName: 'PSD', category: 'image' },
raw: { extension: 'raw', mimeType: 'image/x-raw', displayName: 'RAW', category: 'image' },
tga: { extension: 'tga', mimeType: 'image/x-tga', displayName: 'TGA', category: 'image' },
// Audio - Common
mp3: { extension: 'mp3', mimeType: 'audio/mpeg', displayName: 'MP3', category: 'audio' }, mp3: { extension: 'mp3', mimeType: 'audio/mpeg', displayName: 'MP3', category: 'audio' },
wav: { extension: 'wav', mimeType: 'audio/wav', displayName: 'WAV', category: 'audio' }, wav: { extension: 'wav', mimeType: 'audio/wav', displayName: 'WAV', category: 'audio' },
ogg: { extension: 'ogg', mimeType: 'audio/ogg', displayName: 'OGG', category: 'audio' }, ogg: { extension: 'ogg', mimeType: 'audio/ogg', displayName: 'OGG Vorbis', category: 'audio' },
m4a: { extension: 'm4a', mimeType: 'audio/mp4', displayName: 'M4A (AAC)', category: 'audio' }, m4a: { extension: 'm4a', mimeType: 'audio/mp4', displayName: 'M4A', category: 'audio' },
flac: { extension: 'flac', mimeType: 'audio/flac', displayName: 'FLAC', category: 'audio' }, flac: { extension: 'flac', mimeType: 'audio/flac', displayName: 'FLAC', category: 'audio' },
// Audio - New formats
aac: { extension: 'aac', mimeType: 'audio/aac', displayName: 'AAC', category: 'audio' },
aiff: { extension: 'aiff', mimeType: 'audio/aiff', displayName: 'AIFF', category: 'audio' },
alac: { extension: 'm4a', mimeType: 'audio/x-m4a', displayName: 'ALAC', category: 'audio' },
wma: { extension: 'wma', mimeType: 'audio/x-ms-wma', displayName: 'WMA', category: 'audio' },
opus: { extension: 'opus', mimeType: 'audio/opus', displayName: 'Opus', category: 'audio' },
m4r: { extension: 'm4r', mimeType: 'audio/x-m4r', displayName: 'M4R (Ringtone)', category: 'audio' },
amr: { extension: 'amr', mimeType: 'audio/amr', displayName: 'AMR', category: 'audio' },
}; };
// MIME type to format reverse lookup // MIME type to format reverse lookup
export const MIME_TO_FORMAT: Record<string, SupportedFormat> = { export const MIME_TO_FORMAT: Record<string, SupportedFormat> = {
// Images
'image/jpeg': 'jpeg', 'image/jpeg': 'jpeg',
'image/png': 'png', 'image/png': 'png',
'image/webp': 'webp', 'image/webp': 'webp',
@@ -49,15 +73,31 @@ export const MIME_TO_FORMAT: Record<string, SupportedFormat> = {
'image/heif': 'heic', 'image/heif': 'heic',
'image/x-icon': 'ico', 'image/x-icon': 'ico',
'image/vnd.microsoft.icon': 'ico', 'image/vnd.microsoft.icon': 'ico',
'image/avif': 'avif',
'image/jxl': 'jxl',
'image/svg+xml': 'svg',
'image/vnd.adobe.photoshop': 'psd',
'image/x-raw': 'raw',
'image/x-tga': 'tga',
'image/x-targa': 'tga',
// Audio
'audio/mpeg': 'mp3', 'audio/mpeg': 'mp3',
'audio/mp3': 'mp3', 'audio/mp3': 'mp3',
'audio/wav': 'wav', 'audio/wav': 'wav',
'audio/wave': 'wav', 'audio/wave': 'wav',
'audio/x-wav': 'wav',
'audio/ogg': 'ogg', 'audio/ogg': 'ogg',
'audio/mp4': 'm4a', 'audio/mp4': 'm4a',
'audio/x-m4a': 'm4a', 'audio/x-m4a': 'm4a',
'audio/aac': 'm4a',
'audio/flac': 'flac', 'audio/flac': 'flac',
'audio/x-flac': 'flac',
'audio/aac': 'aac',
'audio/aiff': 'aiff',
'audio/x-aiff': 'aiff',
'audio/x-ms-wma': 'wma',
'audio/opus': 'opus',
'audio/x-m4r': 'm4r',
'audio/amr': 'amr',
}; };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

View File

@@ -52,18 +52,18 @@
--card-foreground: oklch(0.145 0 0); --card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0); --popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.55 0.2 280); --primary: oklch(0.6927 0.1596 55.43);
--primary-foreground: oklch(0.985 0 0); --primary-foreground: oklch(0.98 0 0);
--secondary: oklch(0.97 0 0); --secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0); --secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0); --muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0); --muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.95 0.05 280); --accent: oklch(0.95 0.05 55);
--accent-foreground: oklch(0.205 0 0); --accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0); --border: oklch(0.922 0 0);
--input: oklch(0.922 0 0); --input: oklch(0.922 0 0);
--ring: oklch(0.55 0.2 280); --ring: oklch(0.6927 0.1596 55.43);
--chart-1: oklch(0.646 0.222 41.116); --chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704); --chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392); --chart-3: oklch(0.398 0.07 227.392);
@@ -71,46 +71,46 @@
--chart-5: oklch(0.769 0.188 70.08); --chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0); --sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0); --sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.55 0.2 280); --sidebar-primary: oklch(0.6927 0.1596 55.43);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0); --sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0); --sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.55 0.2 280); --sidebar-ring: oklch(0.6927 0.1596 55.43);
} }
.dark { .dark {
--background: oklch(0.12 0.02 280); --background: oklch(0.12 0.02 55);
--foreground: oklch(0.95 0 0); --foreground: oklch(0.95 0 0);
--card: oklch(0.16 0.02 280); --card: oklch(0.16 0.02 55);
--card-foreground: oklch(0.95 0 0); --card-foreground: oklch(0.95 0 0);
--popover: oklch(0.16 0.02 280); --popover: oklch(0.16 0.02 55);
--popover-foreground: oklch(0.95 0 0); --popover-foreground: oklch(0.95 0 0);
--primary: oklch(0.7 0.22 280); --primary: oklch(0.7 0.2 55);
--primary-foreground: oklch(0.12 0.02 280); --primary-foreground: oklch(0.12 0.02 55);
--secondary: oklch(0.22 0.03 280); --secondary: oklch(0.22 0.03 55);
--secondary-foreground: oklch(0.95 0 0); --secondary-foreground: oklch(0.95 0 0);
--muted: oklch(0.22 0.03 280); --muted: oklch(0.22 0.03 55);
--muted-foreground: oklch(0.65 0 0); --muted-foreground: oklch(0.65 0 0);
--accent: oklch(0.28 0.06 280); --accent: oklch(0.28 0.06 55);
--accent-foreground: oklch(0.95 0 0); --accent-foreground: oklch(0.95 0 0);
--destructive: oklch(0.65 0.22 25); --destructive: oklch(0.65 0.22 25);
--border: oklch(1 0 0 / 12%); --border: oklch(1 0 0 / 12%);
--input: oklch(1 0 0 / 15%); --input: oklch(1 0 0 / 15%);
--ring: oklch(0.7 0.22 280); --ring: oklch(0.7 0.2 55);
--chart-1: oklch(0.7 0.22 280); --chart-1: oklch(0.7 0.2 55);
--chart-2: oklch(0.696 0.17 162.48); --chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08); --chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9); --chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439); --chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.14 0.02 280); --sidebar: oklch(0.14 0.02 55);
--sidebar-foreground: oklch(0.95 0 0); --sidebar-foreground: oklch(0.95 0 0);
--sidebar-primary: oklch(0.7 0.22 280); --sidebar-primary: oklch(0.7 0.2 55);
--sidebar-primary-foreground: oklch(0.12 0.02 280); --sidebar-primary-foreground: oklch(0.12 0.02 55);
--sidebar-accent: oklch(0.22 0.03 280); --sidebar-accent: oklch(0.22 0.03 55);
--sidebar-accent-foreground: oklch(0.95 0 0); --sidebar-accent-foreground: oklch(0.95 0 0);
--sidebar-border: oklch(1 0 0 / 12%); --sidebar-border: oklch(1 0 0 / 12%);
--sidebar-ring: oklch(0.7 0.22 280); --sidebar-ring: oklch(0.7 0.2 55);
} }
@layer base { @layer base {

View File

@@ -7,7 +7,10 @@ import * as Comlink from 'comlink';
import { FFmpeg } from '@ffmpeg/ffmpeg'; import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util'; import { toBlobURL } from '@ffmpeg/util';
type AudioFormat = 'mp3' | 'wav' | 'ogg' | 'm4a' | 'flac'; // All supported audio formats
type AudioFormat =
| 'mp3' | 'wav' | 'ogg' | 'm4a' | 'flac'
| 'aac' | 'aiff' | 'alac' | 'wma' | 'opus' | 'm4r' | 'amr';
let ffmpeg: FFmpeg | null = null; let ffmpeg: FFmpeg | null = null;
let ffmpegLoading: Promise<void> | null = null; let ffmpegLoading: Promise<void> | null = null;
@@ -30,6 +33,8 @@ async function ensureFFmpegLoaded(): Promise<FFmpeg> {
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
}); });
console.log('[AudioWorker] FFmpeg WASM initialized');
})(); })();
await ffmpegLoading; await ffmpegLoading;
@@ -42,34 +47,66 @@ function getFFmpegArgs(
bitrate?: number bitrate?: number
): string[] { ): string[] {
const args: string[] = []; const args: string[] = [];
const br = bitrate || 192;
// Codec selection based on output format // Codec selection based on output format
switch (outputFormat) { switch (outputFormat) {
case 'mp3': case 'mp3':
args.push('-c:a', 'libmp3lame'); args.push('-c:a', 'libmp3lame');
args.push('-b:a', `${bitrate || 192}k`); args.push('-b:a', `${br}k`);
break; break;
case 'wav': case 'wav':
args.push('-c:a', 'pcm_s16le'); args.push('-c:a', 'pcm_s16le');
break; break;
case 'ogg': case 'ogg':
args.push('-c:a', 'libvorbis'); args.push('-c:a', 'libvorbis');
args.push('-q:a', '4'); // Quality level 0-10 args.push('-q:a', '4');
break; break;
case 'm4a': case 'm4a':
args.push('-c:a', 'aac'); args.push('-c:a', 'aac');
args.push('-b:a', `${bitrate || 192}k`); args.push('-b:a', `${br}k`);
break; break;
case 'flac': case 'flac':
args.push('-c:a', 'flac'); args.push('-c:a', 'flac');
args.push('-compression_level', '5'); args.push('-compression_level', '5');
break; break;
case 'aac':
args.push('-c:a', 'aac');
args.push('-b:a', `${br}k`);
break;
case 'aiff':
args.push('-c:a', 'pcm_s16be');
break;
case 'alac':
// ALAC in M4A container
args.push('-c:a', 'alac');
break;
case 'wma':
args.push('-c:a', 'wmav2');
args.push('-b:a', `${br}k`);
break;
case 'opus':
args.push('-c:a', 'libopus');
args.push('-b:a', `${br}k`);
break;
case 'm4r':
// M4R is just AAC with different extension (iPhone ringtones)
args.push('-c:a', 'aac');
args.push('-b:a', `${br}k`);
break;
case 'amr':
args.push('-c:a', 'libopencore_amrnb');
args.push('-ar', '8000'); // AMR requires 8kHz sample rate
args.push('-ac', '1'); // AMR is mono only
break;
} }
return args; return args;
} }
function getExtension(format: AudioFormat): string { function getExtension(format: AudioFormat): string {
// ALAC uses m4a container
if (format === 'alac') return 'm4a';
return format; return format;
} }
@@ -80,11 +117,31 @@ function getMimeType(format: AudioFormat): string {
ogg: 'audio/ogg', ogg: 'audio/ogg',
m4a: 'audio/mp4', m4a: 'audio/mp4',
flac: 'audio/flac', flac: 'audio/flac',
aac: 'audio/aac',
aiff: 'audio/aiff',
alac: 'audio/x-m4a',
wma: 'audio/x-ms-wma',
opus: 'audio/opus',
m4r: 'audio/x-m4r',
amr: 'audio/amr',
}; };
return mimeMap[format]; return mimeMap[format];
} }
const audioConverter = { const audioConverter = {
/**
* Initialize the worker (can be called early to warm up)
*/
async init(): Promise<boolean> {
try {
await ensureFFmpegLoaded();
return true;
} catch (error) {
console.error('[AudioWorker] Init failed:', error);
return false;
}
},
/** /**
* Convert audio file using FFmpeg * Convert audio file using FFmpeg
*/ */

View File

@@ -13,7 +13,10 @@ import {
MagickGeometry, MagickGeometry,
} from '@imagemagick/magick-wasm'; } from '@imagemagick/magick-wasm';
type ImageFormat = 'jpeg' | 'png' | 'webp' | 'gif' | 'bmp' | 'tiff' | 'heic' | 'ico'; // All supported image formats
type ImageFormat =
| 'jpeg' | 'png' | 'webp' | 'gif' | 'bmp' | 'tiff' | 'heic' | 'ico'
| 'avif' | 'jxl' | 'svg' | 'psd' | 'raw' | 'tga';
let magickInitialized = false; let magickInitialized = false;
let initPromise: Promise<void> | null = null; let initPromise: Promise<void> | null = null;
@@ -46,6 +49,12 @@ function getMagickFormat(format: ImageFormat): MagickFormat {
tiff: MagickFormat.Tiff, tiff: MagickFormat.Tiff,
heic: MagickFormat.Heic, heic: MagickFormat.Heic,
ico: MagickFormat.Ico, ico: MagickFormat.Ico,
avif: MagickFormat.Avif,
jxl: MagickFormat.Jxl,
svg: MagickFormat.Svg,
psd: MagickFormat.Psd,
raw: MagickFormat.Raw, // Note: May need specific raw format handling
tga: MagickFormat.Tga,
}; };
return formatMap[format]; return formatMap[format];
} }
@@ -60,6 +69,12 @@ function getMimeType(format: ImageFormat): string {
tiff: 'image/tiff', tiff: 'image/tiff',
heic: 'image/heic', heic: 'image/heic',
ico: 'image/x-icon', ico: 'image/x-icon',
avif: 'image/avif',
jxl: 'image/jxl',
svg: 'image/svg+xml',
psd: 'image/vnd.adobe.photoshop',
raw: 'image/x-raw',
tga: 'image/x-tga',
}; };
return mimeMap[format]; return mimeMap[format];
} }
@@ -103,7 +118,7 @@ const imageConverter = {
} }
// Set quality for lossy formats // Set quality for lossy formats
if (outputFormat === 'jpeg' || outputFormat === 'webp') { if (['jpeg', 'webp', 'avif', 'jxl'].includes(outputFormat)) {
image.quality = quality; image.quality = quality;
} }
@@ -143,7 +158,7 @@ const imageConverter = {
geometry.ignoreAspectRatio = false; geometry.ignoreAspectRatio = false;
image.resize(geometry); image.resize(geometry);
if (outputFormat === 'jpeg' || outputFormat === 'webp') { if (['jpeg', 'webp', 'avif', 'jxl'].includes(outputFormat)) {
image.quality = quality; image.quality = quality;
} }