import { useState } from 'react'; import { useEditorStore } from '../store/editorStore'; import { Loader2, Scissors } from 'lucide-react'; type SilenceRange = { start: number; end: number; duration: number; }; export default function SilenceTrimmerPanel() { const { videoPath, backendUrl, addCutRange, duration } = useEditorStore(); const [minSilenceMs, setMinSilenceMs] = useState(500); const [silenceDb, setSilenceDb] = useState(-35); const [preBufferMs, setPreBufferMs] = useState(80); const [postBufferMs, setPostBufferMs] = useState(120); const [isDetecting, setIsDetecting] = useState(false); const [ranges, setRanges] = useState([]); const detectSilence = async () => { if (!videoPath) return; setIsDetecting(true); setRanges([]); try { const res = await fetch(`${backendUrl}/audio/detect-silence`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input_path: videoPath, min_silence_ms: minSilenceMs, silence_db: silenceDb, }), }); if (!res.ok) { let detail = `HTTP ${res.status} ${res.statusText}`; try { const err = await res.json(); if (err?.detail) detail += ` - ${String(err.detail)}`; } catch { // ignore JSON parse errors for non-JSON error responses } if (res.status === 404) { detail += ' (endpoint missing: restart backend to load /audio/detect-silence)'; } throw new Error(detail); } const data = await res.json(); setRanges(data.ranges || []); } catch (err) { console.error(err); const message = err instanceof Error ? err.message : 'Unknown error'; alert(`Silence detection failed: ${message}`); } finally { setIsDetecting(false); } }; const applyAsCuts = () => { const preBufferSeconds = preBufferMs / 1000; const postBufferSeconds = postBufferMs / 1000; const maxEnd = duration > 0 ? duration : Number.POSITIVE_INFINITY; for (const r of ranges) { // Positive buffers shrink the cut, negative buffers expand it. const start = Math.max(0, r.start + preBufferSeconds); const end = Math.min(maxEnd, r.end - postBufferSeconds); if (end - start >= 0.01) { addCutRange(start, end); } } }; return (

Silence / Pause Trimmer

Detect pauses and convert them into cut ranges.

setMinSilenceMs(Number(e.target.value) || 500)} className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" />
setSilenceDb(Number(e.target.value) || -35)} className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" />
setPreBufferMs(Number(e.target.value) || 0)} className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" />
setPostBufferMs(Number(e.target.value) || 0)} className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" />
{ranges.length > 0 && (
Detected {ranges.length} pause ranges
{ranges.slice(0, 50).map((r, i) => (
{r.start.toFixed(2)}s - {r.end.toFixed(2)}s ({r.duration.toFixed(2)}s)
))}
)}
); }