import { useState, useCallback, useMemo } from 'react'; import { useEditorStore } from '../store/editorStore'; import { Download, Loader2, Zap, Cog, Info } from 'lucide-react'; import type { ExportOptions } from '../types/project'; export default function ExportDialog() { const { videoPath, words, deletedRanges, cutRanges, muteRanges, isExporting, exportProgress, backendUrl, setExporting, getKeepSegments } = useEditorStore(); const hasCuts = deletedRanges.length > 0; const [options, setOptions] = useState>({ mode: 'fast', resolution: '1080p', format: 'mp4', enhanceAudio: false, captions: 'none', }); const handleExport = useCallback(async () => { if (!videoPath) return; const outputPath = await window.electronAPI?.saveFile({ defaultPath: videoPath.replace(/\.[^.]+$/, '_edited.mp4'), filters: [ { name: 'MP4', extensions: ['mp4'] }, { name: 'MOV', extensions: ['mov'] }, { name: 'WebM', extensions: ['webm'] }, ], }); if (!outputPath) return; setExporting(true, 0); try { const keepSegments = getKeepSegments(); const deletedSet = new Set(); for (const range of deletedRanges) { for (const idx of range.wordIndices) deletedSet.add(idx); } const res = await fetch(`${backendUrl}/export`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input_path: videoPath, output_path: outputPath, keep_segments: keepSegments, mute_ranges: muteRanges, words: options.captions !== 'none' ? words : undefined, deleted_indices: options.captions !== 'none' ? [...deletedSet] : undefined, ...options, }), }); if (!res.ok) throw new Error(`Export failed: ${res.statusText}`); setExporting(false, 100); } catch (err) { console.error('Export error:', err); setExporting(false); } }, [videoPath, options, backendUrl, setExporting, getKeepSegments]); return (

Export Video

{/* Mode */}
Export Mode
setOptions((o) => ({ ...o, mode: 'fast' }))} icon={} title="Fast" desc="Stream copy, seconds" /> setOptions((o) => ({ ...o, mode: 'reencode' }))} icon={} title="Re-encode" desc="Custom quality, slower" />
{/* Resolution (only for re-encode) */} {options.mode === 'reencode' && ( setOptions((o) => ({ ...o, resolution: v as ExportOptions['resolution'] }))} options={[ { value: '720p', label: '720p (HD)' }, { value: '1080p', label: '1080p (Full HD)' }, { value: '4k', label: '4K (Ultra HD)' }, ]} /> )} {/* Format */} setOptions((o) => ({ ...o, format: v as ExportOptions['format'] }))} options={[ { value: 'mp4', label: 'MP4 (H.264)' }, { value: 'mov', label: 'MOV (QuickTime)' }, { value: 'webm', label: 'WebM (VP9)' }, ]} /> {/* Audio enhancement */} {/* Captions */} setOptions((o) => ({ ...o, captions: v as ExportOptions['captions'] }))} options={[ { value: 'none', label: 'No captions' }, { value: 'burn-in', label: 'Burn-in (permanent)' }, { value: 'sidecar', label: 'Sidecar SRT file' }, ]} /> {/* Export button */} {options.mode === 'fast' && !hasCuts && (

Fast mode uses stream copy — no quality loss, exports in seconds.

)} {options.mode === 'fast' && hasCuts && (
Word-level cuts require re-encoding for frame-accurate output. Export will automatically use re-encode mode. This takes longer but ensures your cuts are precise.
)}
); } function ModeCard({ active, onClick, icon, title, desc, }: { active: boolean; onClick: () => void; icon: React.ReactNode; title: string; desc: string; }) { return ( ); } function SelectField({ label, value, onChange, options, }: { label: string; value: string; onChange: (value: string) => void; options: Array<{ value: string; label: string }>; }) { return (
); }