feat: Expand supported image and audio formats, update UI color palette, and refresh application assets.
This commit is contained in:
@@ -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
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -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 |
@@ -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';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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',
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user