diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a041d4c..58922d5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -48,9 +48,12 @@ export default function App() { language, isTranscribing, transcriptionStatus, + markInTime, + markOutTime, loadVideo, setProjectFilePath, setBackendUrl, + clearMarkRange, setTranscription, setTranscriptionModel, setTranscribing, @@ -333,6 +336,17 @@ export default function App() { }; const handleCut = () => { + if (markInTime !== null && markOutTime !== null) { + const startTime = Math.min(markInTime, markOutTime); + const endTime = Math.max(markInTime, markOutTime); + if (endTime - startTime >= 0.01) { + addCutRange(startTime, endTime); + setActivePanel('zones'); + } + clearMarkRange(); + return; + } + if (selectedWordIndices.length > 0) { // If words are selected, apply cut immediately const sorted = [...selectedWordIndices].sort((a, b) => a - b); @@ -349,6 +363,17 @@ export default function App() { }; const handleMute = () => { + if (markInTime !== null && markOutTime !== null) { + const startTime = Math.min(markInTime, markOutTime); + const endTime = Math.max(markInTime, markOutTime); + if (endTime - startTime >= 0.01) { + addMuteRange(startTime, endTime); + setActivePanel('zones'); + } + clearMarkRange(); + return; + } + if (selectedWordIndices.length > 0) { // If words are selected, apply mute immediately const sorted = [...selectedWordIndices].sort((a, b) => a - b); @@ -365,6 +390,17 @@ export default function App() { }; const handleGain = () => { + if (markInTime !== null && markOutTime !== null) { + const startTime = Math.min(markInTime, markOutTime); + const endTime = Math.max(markInTime, markOutTime); + if (endTime - startTime >= 0.01) { + addGainRange(startTime, endTime, gainModeDb); + setActivePanel('zones'); + } + clearMarkRange(); + return; + } + if (selectedWordIndices.length > 0) { const sorted = [...selectedWordIndices].sort((a, b) => a - b); const startTime = words[sorted[0]].start; @@ -379,6 +415,17 @@ export default function App() { }; const handleSpeed = () => { + if (markInTime !== null && markOutTime !== null) { + const startTime = Math.min(markInTime, markOutTime); + const endTime = Math.max(markInTime, markOutTime); + if (endTime - startTime >= 0.01) { + addSpeedRange(startTime, endTime, speedModeValue); + setActivePanel('zones'); + } + clearMarkRange(); + return; + } + if (selectedWordIndices.length > 0) { const sorted = [...selectedWordIndices].sort((a, b) => a - b); const startTime = words[sorted[0]].start; diff --git a/frontend/src/components/WaveformTimeline.tsx b/frontend/src/components/WaveformTimeline.tsx index 4dee0b1..31deff7 100644 --- a/frontend/src/components/WaveformTimeline.tsx +++ b/frontend/src/components/WaveformTimeline.tsx @@ -278,11 +278,13 @@ export default function WaveformTimeline({ const [showMuteZones, setShowMuteZones] = useState(true); const [showGainZones, setShowGainZones] = useState(true); const [showSpeedZones, setShowSpeedZones] = useState(true); + const [showAdjustedTimeline, setShowAdjustedTimeline] = useState(false); const sourceDuration = duration || waveformDataRef.current?.duration || 0; + const timelineCutRanges = showAdjustedTimeline ? cutRanges : []; const { segments: timelineSegments, displayDuration } = useMemo( - () => buildTimelineSegments(sourceDuration, cutRanges), - [sourceDuration, cutRanges], + () => buildTimelineSegments(sourceDuration, timelineCutRanges), + [sourceDuration, timelineCutRanges], ); useEffect(() => { @@ -447,15 +449,16 @@ export default function WaveformTimeline({ const x1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec; const x2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec; const isSelected = selectedZone?.type === 'cut' && selectedZone.id === range.id; - - ctx.fillStyle = isSelected ? 'rgba(239, 68, 68, 0.5)' : 'rgba(239, 68, 68, 0.3)'; + + ctx.fillStyle = isSelected ? 'rgba(239, 68, 68, 0.62)' : 'rgba(239, 68, 68, 0.46)'; ctx.fillRect(x1, waveTop, x2 - x1, waveH); + + // Keep cut ranges clearly visible even when not selected. + ctx.strokeStyle = isSelected ? '#ef4444' : 'rgba(239, 68, 68, 0.9)'; + ctx.lineWidth = isSelected ? 2.8 : 1.8; + ctx.strokeRect(x1, waveTop, x2 - x1, waveH); if (isSelected) { - ctx.strokeStyle = '#ef4444'; - ctx.lineWidth = 2; - ctx.strokeRect(x1, waveTop, x2 - x1, waveH); - // Draw resize handles ctx.fillStyle = '#ef4444'; ctx.beginPath(); @@ -634,6 +637,7 @@ export default function WaveformTimeline({ gainMode, speedMode, selectedZone, + showAdjustedTimeline, markInTime, markOutTime, displayDuration, @@ -1184,35 +1188,47 @@ export default function WaveformTimeline({ {markOutTime !== null && O {markOutTime.toFixed(2)}s}
- - - - - + +
+ Show zones + + + + +
+ Scroll · Ctrl+Scroll to zoom
diff --git a/frontend/src/components/ZoneEditor.tsx b/frontend/src/components/ZoneEditor.tsx index 548db04..ecb0bab 100644 --- a/frontend/src/components/ZoneEditor.tsx +++ b/frontend/src/components/ZoneEditor.tsx @@ -2,6 +2,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useEditorStore } from '../store/editorStore'; import { Trash2, Scissors, Volume2, SlidersHorizontal, Gauge, Play } from 'lucide-react'; +function formatTimelineLikeTime(secs: number): string { + const m = Math.floor(secs / 60); + const s = secs % 60; + if (m > 0) return `${m}:${String(Math.floor(s)).padStart(2, '0')}.${Math.floor((s % 1) * 10)}`; + return `${s.toFixed(1)}s`; +} + export default function ZoneEditor() { const [viewMode, setViewMode] = useState<'all' | 'cut' | 'mute' | 'gain' | 'speed'>('all'); const [focusedZone, setFocusedZone] = useState<{ type: 'cut' | 'mute' | 'gain' | 'speed'; id: string } | null>(null); @@ -256,7 +263,7 @@ export default function ZoneEditor() { >
- {range.start.toFixed(2)}s – {range.end.toFixed(2)}s + {formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
{range.id}
@@ -293,7 +300,7 @@ export default function ZoneEditor() { >
- {range.start.toFixed(2)}s – {range.end.toFixed(2)}s + {formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
{range.id}
@@ -357,7 +364,7 @@ export default function ZoneEditor() { >
- {range.start.toFixed(2)}s – {range.end.toFixed(2)}s + {formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
{range.gainDb > 0 ? '+' : ''}{range.gainDb.toFixed(1)} dB @@ -407,7 +414,7 @@ export default function ZoneEditor() { >
- {range.start.toFixed(2)}s – {range.end.toFixed(2)}s + {formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
{range.speed.toFixed(2)}x