polishing
This commit is contained in:
@ -228,6 +228,7 @@ export default function ExportDialog() {
|
||||
icon={<Zap className="w-4 h-4" />}
|
||||
title="Fast"
|
||||
desc="Stream copy, seconds"
|
||||
tooltip="Stream copy — fast, no quality loss, but does not apply cuts or effects"
|
||||
/>
|
||||
<ModeCard
|
||||
active={options.mode === 'reencode'}
|
||||
@ -235,6 +236,7 @@ export default function ExportDialog() {
|
||||
icon={<Cog className="w-4 h-4" />}
|
||||
title="Re-encode"
|
||||
desc="Custom quality, slower"
|
||||
tooltip="Full re-encode — applies cuts, gain, speed, zoom, captions, and effects"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@ -250,6 +252,7 @@ export default function ExportDialog() {
|
||||
{ value: '1080p', label: '1080p (Full HD)' },
|
||||
{ value: '4k', label: '4K (Ultra HD)' },
|
||||
]}
|
||||
data-tooltip="Output video resolution — higher resolution = larger file"
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -264,6 +267,7 @@ export default function ExportDialog() {
|
||||
{ value: 'webm', label: 'WebM (VP9)' },
|
||||
...(isAudioOnly ? [{ value: 'wav' as const, label: 'WAV (Uncompressed)' }] : []),
|
||||
]}
|
||||
data-tooltip="Output container format — MP4 is most compatible"
|
||||
/>
|
||||
|
||||
{/* Video zoom / punch-in */}
|
||||
@ -274,6 +278,7 @@ export default function ExportDialog() {
|
||||
checked={options.zoom?.enabled || false}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, enabled: e.target.checked } }))}
|
||||
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
|
||||
data-tooltip="Crop and reposition the video frame — useful for removing black bars or reframing"
|
||||
/>
|
||||
<div>
|
||||
<span className="text-xs font-medium flex items-center gap-1">
|
||||
@ -297,6 +302,7 @@ export default function ExportDialog() {
|
||||
value={options.zoom?.zoomFactor || 1}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, zoomFactor: Number(e.target.value) } }))}
|
||||
className="flex-1 h-1.5"
|
||||
data-tooltip="Magnification level — 1.0x is original, higher values zoom in"
|
||||
/>
|
||||
<span className="text-xs text-editor-text w-10 text-right">{options.zoom?.zoomFactor?.toFixed(2)}x</span>
|
||||
</div>
|
||||
@ -310,6 +316,7 @@ export default function ExportDialog() {
|
||||
value={options.zoom?.panX || 0}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, panX: Number(e.target.value) } }))}
|
||||
className="flex-1 h-1.5"
|
||||
data-tooltip="Horizontal position of the crop window — negative moves left, positive moves right"
|
||||
/>
|
||||
<span className="text-xs text-editor-text w-10 text-right">{((options.zoom?.panX || 0) * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
@ -323,6 +330,7 @@ export default function ExportDialog() {
|
||||
value={options.zoom?.panY || 0}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, panY: Number(e.target.value) } }))}
|
||||
className="flex-1 h-1.5"
|
||||
data-tooltip="Vertical position of the crop window — negative moves up, positive moves down"
|
||||
/>
|
||||
<span className="text-xs text-editor-text w-10 text-right">{((options.zoom?.panY || 0) * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
@ -339,6 +347,7 @@ export default function ExportDialog() {
|
||||
checked={options.removeBackground || false}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, removeBackground: e.target.checked }))}
|
||||
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
|
||||
data-tooltip="Remove or replace the background behind the speaker"
|
||||
/>
|
||||
<div>
|
||||
<span className="text-xs font-medium flex items-center gap-1">
|
||||
@ -407,6 +416,7 @@ export default function ExportDialog() {
|
||||
checked={options.normalizeAudio}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, normalizeAudio: e.target.checked }))}
|
||||
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
|
||||
data-tooltip="Normalize audio to a consistent loudness target"
|
||||
/>
|
||||
<div>
|
||||
<span className="text-xs font-medium">Normalize loudness</span>
|
||||
@ -422,6 +432,7 @@ export default function ExportDialog() {
|
||||
value={options.normalizeTarget}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, normalizeTarget: Number(e.target.value) }))}
|
||||
className="flex-1 px-2 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:outline-none focus:border-editor-accent [color-scheme:dark]"
|
||||
data-tooltip="Loudness target — YouTube (-14), Spotify (-16), Broadcast (-23)"
|
||||
>
|
||||
<option value={-14}>YouTube (-14 LUFS)</option>
|
||||
<option value={-16}>Spotify (-16 LUFS)</option>
|
||||
@ -440,6 +451,7 @@ export default function ExportDialog() {
|
||||
checked={options.enhanceAudio}
|
||||
onChange={(e) => setOptions((o) => ({ ...o, enhanceAudio: e.target.checked }))}
|
||||
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
|
||||
data-tooltip="Apply noise reduction and speech enhancement"
|
||||
/>
|
||||
<span className="text-xs">Enhance audio (Studio Sound)</span>
|
||||
</label>
|
||||
@ -454,6 +466,7 @@ export default function ExportDialog() {
|
||||
{ value: 'burn-in', label: 'Burn-in (permanent)' },
|
||||
{ value: 'sidecar', label: 'Sidecar SRT file' },
|
||||
]}
|
||||
data-tooltip="Burn captions into video, export as separate SRT/VTT file, or none"
|
||||
/>
|
||||
|
||||
{/* Transcript-only export */}
|
||||
@ -478,6 +491,7 @@ export default function ExportDialog() {
|
||||
onClick={handleTranscriptExport}
|
||||
disabled={isTranscribingTranscript || words.length === 0}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30 disabled:opacity-40 transition-colors"
|
||||
data-tooltip="Export just the transcript text or subtitles without the video"
|
||||
>
|
||||
{isTranscribingTranscript ? (
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
@ -494,6 +508,7 @@ export default function ExportDialog() {
|
||||
onClick={handleExport}
|
||||
disabled={isExporting || !videoPath}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-semibold transition-colors"
|
||||
data-tooltip="Start export with current settings"
|
||||
>
|
||||
{isExporting ? (
|
||||
<>
|
||||
@ -538,16 +553,19 @@ function ModeCard({
|
||||
icon,
|
||||
title,
|
||||
desc,
|
||||
tooltip,
|
||||
}: {
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
desc: string;
|
||||
tooltip?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
title={tooltip}
|
||||
className={`flex flex-col items-center gap-1 p-3 rounded-lg border-2 transition-colors ${
|
||||
active
|
||||
? 'border-editor-accent bg-editor-accent/10'
|
||||
@ -566,16 +584,19 @@ function SelectField({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
title,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
options: Array<{ value: string; label: string }>;
|
||||
title?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-editor-text-muted font-medium">{label}</label>
|
||||
<select
|
||||
title={title}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-editor-surface border border-editor-border rounded-lg text-xs text-editor-text focus:outline-none focus:border-editor-accent [color-scheme:dark]"
|
||||
|
||||
Reference in New Issue
Block a user