improved feature 11 and UI

This commit is contained in:
2026-04-15 21:25:47 -06:00
parent 3fa67383c4
commit 168676a9e9
3 changed files with 111 additions and 41 deletions

View File

@ -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;

View File

@ -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(() => {
@ -448,14 +450,15 @@ export default function WaveformTimeline({
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);
if (isSelected) {
ctx.strokeStyle = '#ef4444';
ctx.lineWidth = 2;
// 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) {
// Draw resize handles
ctx.fillStyle = '#ef4444';
ctx.beginPath();
@ -634,6 +637,7 @@ export default function WaveformTimeline({
gainMode,
speedMode,
selectedZone,
showAdjustedTimeline,
markInTime,
markOutTime,
displayDuration,
@ -1184,6 +1188,17 @@ export default function WaveformTimeline({
{markOutTime !== null && <span className="text-[10px] text-yellow-300">O {markOutTime.toFixed(2)}s</span>}
</div>
<div className="flex items-center gap-2">
<label className="flex items-center gap-1 text-[10px] text-editor-text-muted select-none">
<input
type="checkbox"
checked={showAdjustedTimeline}
onChange={(e) => setShowAdjustedTimeline(e.target.checked)}
className="h-3 w-3 rounded border-editor-border bg-editor-surface"
/>
Show adjusted timeline
</label>
<div className="flex items-center gap-2 pl-2 ml-1 border-l border-editor-border/80">
<span className="text-[10px] text-editor-text-muted">Show zones</span>
<button
onClick={() => setShowCutZones((v) => !v)}
className={`px-1.5 py-0.5 rounded text-[10px] border ${showCutZones ? 'border-red-500/60 text-red-300 bg-red-500/10' : 'border-editor-border text-editor-text-muted'}`}
@ -1212,7 +1227,8 @@ export default function WaveformTimeline({
>
Speed
</button>
<span className="text-[10px] text-editor-text-muted">
</div>
<span className="text-[10px] text-editor-text-muted pl-2 ml-1 border-l border-editor-border/80">
Scroll · Ctrl+Scroll to zoom
</span>
</div>

View File

@ -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() {
>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">
{range.start.toFixed(2)}s {range.end.toFixed(2)}s
{formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
</div>
<div className="text-editor-text-muted text-[10px]">{range.id}</div>
</div>
@ -293,7 +300,7 @@ export default function ZoneEditor() {
>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">
{range.start.toFixed(2)}s {range.end.toFixed(2)}s
{formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
</div>
<div className="text-editor-text-muted text-[10px]">{range.id}</div>
</div>
@ -357,7 +364,7 @@ export default function ZoneEditor() {
>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">
{range.start.toFixed(2)}s {range.end.toFixed(2)}s
{formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
</div>
<div className="text-editor-text-muted text-[10px]">
{range.gainDb > 0 ? '+' : ''}{range.gainDb.toFixed(1)} dB
@ -407,7 +414,7 @@ export default function ZoneEditor() {
>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">
{range.start.toFixed(2)}s {range.end.toFixed(2)}s
{formatTimelineLikeTime(range.start)} - {formatTimelineLikeTime(range.end)}
</div>
<div className="text-editor-text-muted text-[10px]">
{range.speed.toFixed(2)}x