290 lines
11 KiB
TypeScript
290 lines
11 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
import { Card } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Slider } from '@/components/ui/slider';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import type { ConversionOptions, ConversionFormat } from '@/types/media';
|
|
|
|
interface ConversionOptionsProps {
|
|
inputFormat: ConversionFormat;
|
|
outputFormat: ConversionFormat;
|
|
options: ConversionOptions;
|
|
onOptionsChange: (options: ConversionOptions) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export function ConversionOptionsPanel({
|
|
inputFormat,
|
|
outputFormat,
|
|
options,
|
|
onOptionsChange,
|
|
disabled = false,
|
|
}: ConversionOptionsProps) {
|
|
const [isExpanded, setIsExpanded] = React.useState(false);
|
|
|
|
const handleOptionChange = (key: string, value: any) => {
|
|
onOptionsChange({ ...options, [key]: value });
|
|
};
|
|
|
|
const renderVideoOptions = () => (
|
|
<div className="space-y-4">
|
|
{/* Video Codec */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground block">Video Codec</label>
|
|
<Select
|
|
value={options.videoCodec || 'default'}
|
|
onValueChange={(value) => handleOptionChange('videoCodec', value === 'default' ? undefined : value)}
|
|
disabled={disabled}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select video codec" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="default">Auto (Recommended)</SelectItem>
|
|
<SelectItem value="libx264">H.264 (MP4, AVI, MOV)</SelectItem>
|
|
<SelectItem value="libx265">H.265 (MP4)</SelectItem>
|
|
<SelectItem value="libvpx">VP8 (WebM)</SelectItem>
|
|
<SelectItem value="libvpx-vp9">VP9 (WebM)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Video Bitrate */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<label className="text-sm font-medium text-foreground">Video Bitrate</label>
|
|
<span className="text-xs text-muted-foreground">{options.videoBitrate || '2M'}</span>
|
|
</div>
|
|
<Slider
|
|
min={0.5}
|
|
max={10}
|
|
step={0.5}
|
|
value={[parseFloat(options.videoBitrate?.replace('M', '') || '2')]}
|
|
onValueChange={(vals) => handleOptionChange('videoBitrate', `${vals[0]}M`)}
|
|
disabled={disabled}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">Higher bitrate = better quality, larger file</p>
|
|
</div>
|
|
|
|
{/* Resolution */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground block">Resolution</label>
|
|
<Select
|
|
value={options.videoResolution || 'original'}
|
|
onValueChange={(value) => handleOptionChange('videoResolution', value === 'original' ? undefined : value)}
|
|
disabled={disabled}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select resolution" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="original">Original</SelectItem>
|
|
<SelectItem value="1920x-1">1080p (1920x1080)</SelectItem>
|
|
<SelectItem value="1280x-1">720p (1280x720)</SelectItem>
|
|
<SelectItem value="854x-1">480p (854x480)</SelectItem>
|
|
<SelectItem value="640x-1">360p (640x360)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* FPS */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground block">Frame Rate (FPS)</label>
|
|
<Select
|
|
value={options.videoFps?.toString() || 'original'}
|
|
onValueChange={(value) => handleOptionChange('videoFps', value === 'original' ? undefined : parseInt(value))}
|
|
disabled={disabled}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select frame rate" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="original">Original</SelectItem>
|
|
<SelectItem value="60">60 fps</SelectItem>
|
|
<SelectItem value="30">30 fps</SelectItem>
|
|
<SelectItem value="24">24 fps</SelectItem>
|
|
<SelectItem value="15">15 fps</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Audio Bitrate */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<label className="text-sm font-medium text-foreground">Audio Bitrate</label>
|
|
<span className="text-xs text-muted-foreground">{options.audioBitrate || '128k'}</span>
|
|
</div>
|
|
<Slider
|
|
min={64}
|
|
max={320}
|
|
step={32}
|
|
value={[parseInt(options.audioBitrate?.replace('k', '') || '128')]}
|
|
onValueChange={(vals) => handleOptionChange('audioBitrate', `${vals[0]}k`)}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderAudioOptions = () => (
|
|
<div className="space-y-4">
|
|
{/* Audio Codec */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground block">Audio Codec</label>
|
|
<Select
|
|
value={options.audioCodec || 'default'}
|
|
onValueChange={(value) => handleOptionChange('audioCodec', value === 'default' ? undefined : value)}
|
|
disabled={disabled}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select audio codec" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="default">Auto (Recommended)</SelectItem>
|
|
<SelectItem value="libmp3lame">MP3 (LAME)</SelectItem>
|
|
<SelectItem value="aac">AAC</SelectItem>
|
|
<SelectItem value="libvorbis">Vorbis (OGG)</SelectItem>
|
|
<SelectItem value="libopus">Opus</SelectItem>
|
|
<SelectItem value="flac">FLAC (Lossless)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Bitrate */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<label className="text-sm font-medium text-foreground">Bitrate</label>
|
|
<span className="text-xs text-muted-foreground">{options.audioBitrate || '192k'}</span>
|
|
</div>
|
|
<Slider
|
|
min={64}
|
|
max={320}
|
|
step={32}
|
|
value={[parseInt(options.audioBitrate?.replace('k', '') || '192')]}
|
|
onValueChange={(vals) => handleOptionChange('audioBitrate', `${vals[0]}k`)}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
|
|
{/* Sample Rate */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground block">Sample Rate</label>
|
|
<Select
|
|
value={options.audioSampleRate?.toString() || 'original'}
|
|
onValueChange={(value) => handleOptionChange('audioSampleRate', value === 'original' ? undefined : parseInt(value))}
|
|
disabled={disabled}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select sample rate" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="original">Original</SelectItem>
|
|
<SelectItem value="48000">48 kHz (Studio)</SelectItem>
|
|
<SelectItem value="44100">44.1 kHz (CD Quality)</SelectItem>
|
|
<SelectItem value="22050">22.05 kHz</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Channels */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground block">Channels</label>
|
|
<Select
|
|
value={options.audioChannels?.toString() || 'original'}
|
|
onValueChange={(value) => handleOptionChange('audioChannels', value === 'original' ? undefined : parseInt(value))}
|
|
disabled={disabled}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select channels" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="original">Original</SelectItem>
|
|
<SelectItem value="2">Stereo (2 channels)</SelectItem>
|
|
<SelectItem value="1">Mono (1 channel)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderImageOptions = () => (
|
|
<div className="space-y-4">
|
|
{/* Quality */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<label className="text-sm font-medium text-foreground">Quality</label>
|
|
<span className="text-xs text-muted-foreground">{options.imageQuality || 85}%</span>
|
|
</div>
|
|
<Slider
|
|
min={1}
|
|
max={100}
|
|
step={1}
|
|
value={[options.imageQuality || 85]}
|
|
onValueChange={(vals) => handleOptionChange('imageQuality', vals[0])}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
|
|
{/* Width */}
|
|
<div>
|
|
<label className="text-sm font-medium text-foreground mb-2 block">Width (px)</label>
|
|
<input
|
|
type="number"
|
|
value={options.imageWidth || ''}
|
|
onChange={(e) => handleOptionChange('imageWidth', e.target.value ? parseInt(e.target.value) : undefined)}
|
|
placeholder="Original"
|
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
disabled={disabled}
|
|
/>
|
|
<p className="text-xs text-muted-foreground mt-1">Leave empty to keep original</p>
|
|
</div>
|
|
|
|
{/* Height */}
|
|
<div>
|
|
<label className="text-sm font-medium text-foreground mb-2 block">Height (px)</label>
|
|
<input
|
|
type="number"
|
|
value={options.imageHeight || ''}
|
|
onChange={(e) => handleOptionChange('imageHeight', e.target.value ? parseInt(e.target.value) : undefined)}
|
|
placeholder="Original"
|
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
disabled={disabled}
|
|
/>
|
|
<p className="text-xs text-muted-foreground mt-1">Leave empty to maintain aspect ratio</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<Card className="p-4">
|
|
{/* Advanced Options Toggle */}
|
|
<button
|
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
className="w-full flex items-center justify-between text-sm font-medium text-foreground hover:text-primary transition-colors"
|
|
disabled={disabled}
|
|
>
|
|
<span>Advanced Options</span>
|
|
{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
|
</button>
|
|
|
|
{/* Advanced Options Panel */}
|
|
{isExpanded && (
|
|
<div className="pt-3 mt-3 border-t border-border">
|
|
{outputFormat.category === 'video' && renderVideoOptions()}
|
|
{outputFormat.category === 'audio' && renderAudioOptions()}
|
|
{outputFormat.category === 'image' && renderImageOptions()}
|
|
</div>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|