fix: remove media conversion presets

This commit is contained in:
2026-02-25 19:20:22 +01:00
parent 4668d771c1
commit 56cdb1ae4a
4 changed files with 3 additions and 447 deletions

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import { ChevronDown, ChevronUp, Sparkles } from 'lucide-react'; import { ChevronDown, ChevronUp } from 'lucide-react';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Slider } from '@/components/ui/slider'; import { Slider } from '@/components/ui/slider';
@@ -22,14 +22,6 @@ interface ConversionOptionsProps {
disabled?: boolean; disabled?: boolean;
} }
interface QualityPreset {
id: string;
name: string;
description: string;
icon: string;
options: ConversionOptions;
}
export function ConversionOptionsPanel({ export function ConversionOptionsPanel({
inputFormat, inputFormat,
outputFormat, outputFormat,
@@ -38,141 +30,8 @@ export function ConversionOptionsPanel({
disabled = false, disabled = false,
}: ConversionOptionsProps) { }: ConversionOptionsProps) {
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = React.useState(false);
const [selectedPreset, setSelectedPreset] = React.useState<string | null>(null);
// Quality presets based on output format category
const getPresets = (): QualityPreset[] => {
const category = outputFormat.category;
if (category === 'video') {
return [
{
id: 'high-quality',
name: 'High Quality',
description: 'Best quality, larger file size',
icon: '',
options: {
videoBitrate: '5M',
videoCodec: outputFormat.extension === 'webm' ? 'libvpx' : 'libx264',
audioBitrate: '192k',
audioCodec: outputFormat.extension === 'webm' ? 'libvorbis' : 'aac',
},
},
{
id: 'balanced',
name: 'Balanced',
description: 'Good quality, moderate size',
icon: '',
options: {
videoBitrate: '2M',
videoCodec: outputFormat.extension === 'webm' ? 'libvpx' : 'libx264',
audioBitrate: '128k',
audioCodec: outputFormat.extension === 'webm' ? 'libvorbis' : 'aac',
},
},
{
id: 'small-file',
name: 'Small File',
description: 'Smaller size, lower quality',
icon: '',
options: {
videoBitrate: '1M',
videoCodec: outputFormat.extension === 'webm' ? 'libvpx' : 'libx264',
audioBitrate: '96k',
audioCodec: outputFormat.extension === 'webm' ? 'libvorbis' : 'aac',
},
},
{
id: 'web-optimized',
name: 'Web Optimized',
description: 'Fast loading for web',
icon: '',
options: {
videoBitrate: '1.5M',
videoCodec: 'libvpx',
audioBitrate: '128k',
audioCodec: 'libvorbis',
videoResolution: '720x-1',
},
},
];
} else if (category === 'audio') {
return [
{
id: 'high-quality',
name: 'High Quality',
description: 'Best audio quality',
icon: '',
options: {
audioBitrate: '320k',
audioCodec: outputFormat.extension === 'mp3' ? 'libmp3lame' : 'default',
},
},
{
id: 'balanced',
name: 'Balanced',
description: 'Good quality, smaller size',
icon: '',
options: {
audioBitrate: '192k',
audioCodec: outputFormat.extension === 'mp3' ? 'libmp3lame' : 'default',
},
},
{
id: 'small-file',
name: 'Small File',
description: 'Minimum file size',
icon: '',
options: {
audioBitrate: '128k',
audioCodec: outputFormat.extension === 'mp3' ? 'libmp3lame' : 'default',
},
},
];
} else if (category === 'image') {
return [
{
id: 'high-quality',
name: 'High Quality',
description: 'Best image quality',
icon: '',
options: {
imageQuality: 95,
},
},
{
id: 'balanced',
name: 'Balanced',
description: 'Good quality',
icon: '',
options: {
imageQuality: 85,
},
},
{
id: 'web-optimized',
name: 'Web Optimized',
description: 'Optimized for web',
icon: '',
options: {
imageQuality: 75,
},
},
];
}
return [];
};
const presets = getPresets();
const handlePresetClick = (preset: QualityPreset) => {
setSelectedPreset(preset.id);
onOptionsChange({ ...options, ...preset.options });
};
const handleOptionChange = (key: string, value: any) => { const handleOptionChange = (key: string, value: any) => {
setSelectedPreset(null); // Clear preset when manual change
onOptionsChange({ ...options, [key]: value }); onOptionsChange({ ...options, [key]: value });
}; };
@@ -407,36 +266,10 @@ export function ConversionOptionsPanel({
return ( return (
<Card className="p-4"> <Card className="p-4">
{/* Presets Section */}
{presets.length > 0 && (
<div className="mb-4">
<div className="flex items-center gap-2 mb-3">
<Sparkles className="h-4 w-4 text-primary" />
<h3 className="text-sm font-semibold">Quality Presets</h3>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{presets.map((preset) => (
<Button
key={preset.id}
onClick={() => handlePresetClick(preset)}
variant={selectedPreset === preset.id ? 'default' : 'outline'}
size="sm"
className="flex flex-col h-auto py-3 px-3 text-left items-start"
disabled={disabled}
>
{preset.icon && <span className="text-base mb-1">{preset.icon}</span>}
<span className="text-xs font-medium">{preset.name}</span>
<span className="text-xs text-muted-foreground mt-1">{preset.description}</span>
</Button>
))}
</div>
</div>
)}
{/* Advanced Options Toggle */} {/* Advanced Options Toggle */}
<button <button
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
className="w-full flex items-center justify-between text-sm font-medium text-foreground hover:text-primary transition-colors mb-3" className="w-full flex items-center justify-between text-sm font-medium text-foreground hover:text-primary transition-colors"
disabled={disabled} disabled={disabled}
> >
<span>Advanced Options</span> <span>Advanced Options</span>
@@ -445,7 +278,7 @@ export function ConversionOptionsPanel({
{/* Advanced Options Panel */} {/* Advanced Options Panel */}
{isExpanded && ( {isExpanded && (
<div className="pt-3 border-t border-border"> <div className="pt-3 mt-3 border-t border-border">
{outputFormat.category === 'video' && renderVideoOptions()} {outputFormat.category === 'video' && renderVideoOptions()}
{outputFormat.category === 'audio' && renderAudioOptions()} {outputFormat.category === 'audio' && renderAudioOptions()}
{outputFormat.category === 'image' && renderImageOptions()} {outputFormat.category === 'image' && renderImageOptions()}

View File

@@ -9,7 +9,6 @@ import { FormatSelector } from './FormatSelector';
import { ConversionPreview } from './ConversionPreview'; import { ConversionPreview } from './ConversionPreview';
import { ConversionOptionsPanel } from './ConversionOptions'; import { ConversionOptionsPanel } from './ConversionOptions';
import { FileInfo } from './FileInfo'; import { FileInfo } from './FileInfo';
import { FormatPresets } from './FormatPresets';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { import {
SUPPORTED_FORMATS, SUPPORTED_FORMATS,
@@ -21,7 +20,6 @@ import { convertWithFFmpeg } from '@/lib/media/converters/ffmpegService';
import { convertWithImageMagick } from '@/lib/media/converters/imagemagickService'; import { convertWithImageMagick } from '@/lib/media/converters/imagemagickService';
import { addToHistory } from '@/lib/media/storage/history'; import { addToHistory } from '@/lib/media/storage/history';
import { downloadBlobsAsZip, generateOutputFilename } from '@/lib/media/utils/fileUtils'; import { downloadBlobsAsZip, generateOutputFilename } from '@/lib/media/utils/fileUtils';
import { getPresetById, type FormatPreset } from '@/lib/media/utils/formatPresets';
import type { ConversionJob, ConversionFormat, ConversionOptions } from '@/types/media'; import type { ConversionJob, ConversionFormat, ConversionOptions } from '@/types/media';
export function FileConverter() { export function FileConverter() {
@@ -221,17 +219,6 @@ export function FileConverter() {
setSelectedFiles((prev) => prev.filter((_, i) => i !== index)); setSelectedFiles((prev) => prev.filter((_, i) => i !== index));
}; };
const handlePresetSelect = (preset: FormatPreset) => {
// Find the output format that matches the preset
const format = compatibleFormats.find(f => f.extension === preset.outputFormat);
if (format) {
setOutputFormat(format);
setConversionOptions(preset.options);
toast.success(`Applied ${preset.name} preset`);
}
};
const handleDownloadAll = async () => { const handleDownloadAll = async () => {
if (!outputFormat) return; if (!outputFormat) return;
@@ -393,15 +380,6 @@ export function FileConverter() {
<FileInfo file={selectedFiles[0]} format={inputFormat} /> <FileInfo file={selectedFiles[0]} format={inputFormat} />
)} )}
{/* Format Presets */}
{inputFormat && (
<FormatPresets
inputFormat={inputFormat}
onPresetSelect={handlePresetSelect}
disabled={isConverting}
/>
)}
{/* Format selection */} {/* Format selection */}
{inputFormat && compatibleFormats.length > 0 && ( {inputFormat && compatibleFormats.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto_1fr] gap-4 items-start"> <div className="grid grid-cols-1 md:grid-cols-[1fr_auto_1fr] gap-4 items-start">

View File

@@ -1,67 +0,0 @@
'use client';
import * as React from 'react';
import { Sparkles } from 'lucide-react';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils/cn';
import { getPresetsByCategory, type FormatPreset } from '@/lib/media/utils/formatPresets';
import type { ConversionFormat } from '@/types/media';
interface FormatPresetsProps {
inputFormat: ConversionFormat;
onPresetSelect: (preset: FormatPreset) => void;
disabled?: boolean;
}
export function FormatPresets({ inputFormat, onPresetSelect, disabled = false }: FormatPresetsProps) {
const [selectedPresetId, setSelectedPresetId] = React.useState<string | null>(null);
// Get presets for the input format's category
const presets = getPresetsByCategory(inputFormat.category);
if (presets.length === 0) {
return null;
}
const handlePresetClick = (preset: FormatPreset) => {
setSelectedPresetId(preset.id);
onPresetSelect(preset);
};
return (
<Card className="p-4">
<div className="flex items-center gap-2 mb-4">
<Sparkles className="h-5 w-5 text-primary" />
<h3 className="text-sm font-semibold">Quick Presets</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{presets.map((preset) => (
<Button
key={preset.id}
onClick={() => handlePresetClick(preset)}
variant={selectedPresetId === preset.id ? 'default' : 'outline'}
className={cn(
'h-auto py-4 px-4 flex flex-col items-start text-left gap-2',
selectedPresetId === preset.id && 'ring-2 ring-primary ring-offset-2'
)}
disabled={disabled}
>
<div className="flex items-center gap-2 w-full">
{preset.icon && <span className="text-2xl">{preset.icon}</span>}
<div className="flex-1 min-w-0">
<div className="font-semibold text-sm">{preset.name}</div>
</div>
</div>
<p className="text-xs text-muted-foreground w-full">{preset.description}</p>
</Button>
))}
</div>
<div className="mt-3 text-xs text-muted-foreground">
Select a preset to automatically configure optimal settings for your use case
</div>
</Card>
);
}

View File

@@ -1,188 +0,0 @@
import type { ConversionOptions } from '@/types/media';
export interface FormatPreset {
id: string;
name: string;
description: string;
icon: string;
category: 'video' | 'audio' | 'image';
outputFormat: string;
options: ConversionOptions;
}
/**
* Predefined format presets for common use cases
*/
export const FORMAT_PRESETS: FormatPreset[] = [
// Video Presets
{
id: 'youtube-video',
name: 'YouTube Video',
description: '1080p MP4, optimized for YouTube',
icon: '',
category: 'video',
outputFormat: 'mp4',
options: {
videoCodec: 'libx264',
videoBitrate: '5M',
videoResolution: '1920x-1',
videoFps: 30,
audioCodec: 'aac',
audioBitrate: '192k',
},
},
{
id: 'instagram-video',
name: 'Instagram Video',
description: 'Square 1:1 format for Instagram',
icon: '',
category: 'video',
outputFormat: 'mp4',
options: {
videoCodec: 'libx264',
videoBitrate: '3M',
videoResolution: '1080x-1', // Will be cropped to square
videoFps: 30,
audioCodec: 'aac',
audioBitrate: '128k',
},
},
{
id: 'twitter-video',
name: 'Twitter Video',
description: '720p, optimized for Twitter',
icon: '',
category: 'video',
outputFormat: 'mp4',
options: {
videoCodec: 'libx264',
videoBitrate: '2M',
videoResolution: '1280x-1',
videoFps: 30,
audioCodec: 'aac',
audioBitrate: '128k',
},
},
{
id: 'web-video',
name: 'Web Optimized',
description: 'Small file size for web streaming',
icon: '',
category: 'video',
outputFormat: 'mp4',
options: {
videoCodec: 'libx264',
videoBitrate: '1.5M',
videoResolution: '854x-1',
videoFps: 24,
audioCodec: 'aac',
audioBitrate: '96k',
},
},
// Audio Presets
{
id: 'podcast-audio',
name: 'Podcast',
description: 'MP3, optimized for voice',
icon: '',
category: 'audio',
outputFormat: 'mp3',
options: {
audioCodec: 'libmp3lame',
audioBitrate: '128k',
audioSampleRate: 44100,
audioChannels: 2,
},
},
{
id: 'music-high-quality',
name: 'High Quality Music',
description: 'MP3, 320kbps for music',
icon: '',
category: 'audio',
outputFormat: 'mp3',
options: {
audioCodec: 'libmp3lame',
audioBitrate: '320k',
audioSampleRate: 48000,
audioChannels: 2,
},
},
{
id: 'audiobook',
name: 'Audiobook',
description: 'Mono, small file size',
icon: '',
category: 'audio',
outputFormat: 'mp3',
options: {
audioCodec: 'libmp3lame',
audioBitrate: '64k',
audioSampleRate: 22050,
audioChannels: 1,
},
},
// Image Presets
{
id: 'web-thumbnail',
name: 'Web Thumbnail',
description: 'JPG, 800px width, optimized',
icon: '',
category: 'image',
outputFormat: 'jpg',
options: {
imageQuality: 85,
imageWidth: 800,
},
},
{
id: 'hd-image',
name: 'HD Image',
description: 'PNG, high quality, lossless',
icon: '',
category: 'image',
outputFormat: 'png',
options: {
imageQuality: 100,
},
},
{
id: 'social-media-image',
name: 'Social Media',
description: 'JPG, 1200px, optimized',
icon: '',
category: 'image',
outputFormat: 'jpg',
options: {
imageQuality: 90,
imageWidth: 1200,
},
},
{
id: 'web-optimized-image',
name: 'Web Optimized',
description: 'WebP, small file size',
icon: '',
category: 'image',
outputFormat: 'webp',
options: {
imageQuality: 80,
},
},
];
/**
* Get presets by category
*/
export function getPresetsByCategory(category: 'video' | 'audio' | 'image'): FormatPreset[] {
return FORMAT_PRESETS.filter(preset => preset.category === category);
}
/**
* Get preset by ID
*/
export function getPresetById(id: string): FormatPreset | undefined {
return FORMAT_PRESETS.find(preset => preset.id === id);
}