lyrics-transcriber 0.43.0__py3-none-any.whl → 0.44.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lyrics_transcriber/core/controller.py +58 -24
- lyrics_transcriber/correction/anchor_sequence.py +22 -8
- lyrics_transcriber/correction/corrector.py +47 -3
- lyrics_transcriber/correction/handlers/llm.py +15 -12
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
- lyrics_transcriber/frontend/dist/assets/{index-D0Gr3Ep7.js → index-DVoI6Z16.js} +10799 -7490
- lyrics_transcriber/frontend/dist/assets/index-DVoI6Z16.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/src/App.tsx +4 -4
- lyrics_transcriber/frontend/src/api.ts +37 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +14 -10
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +62 -56
- lyrics_transcriber/frontend/src/components/EditModal.tsx +232 -237
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/GlobalSyncEditor.tsx +675 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +141 -101
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +146 -80
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +22 -13
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +1 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +29 -12
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +21 -4
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -15
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +34 -16
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +186 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +89 -41
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +9 -2
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +28 -3
- lyrics_transcriber/frontend/src/components/shared/types.ts +17 -2
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +63 -14
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +192 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +267 -0
- lyrics_transcriber/frontend/src/main.tsx +7 -1
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types.ts +1 -1
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/lyrics/base_lyrics_provider.py +2 -2
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/generator.py +40 -12
- lyrics_transcriber/output/video.py +18 -8
- lyrics_transcriber/review/server.py +238 -8
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/METADATA +3 -2
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/RECORD +47 -41
- lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +0 -1
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +0 -252
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +0 -110
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/entry_points.txt +0 -0
@@ -7,25 +7,23 @@ import {
|
|
7
7
|
Box,
|
8
8
|
TextField,
|
9
9
|
Button,
|
10
|
-
Typography
|
11
|
-
Menu,
|
12
|
-
MenuItem,
|
10
|
+
Typography
|
13
11
|
} from '@mui/material'
|
14
12
|
import CloseIcon from '@mui/icons-material/Close'
|
15
|
-
import AddIcon from '@mui/icons-material/Add'
|
16
13
|
import DeleteIcon from '@mui/icons-material/Delete'
|
17
|
-
import MergeIcon from '@mui/icons-material/CallMerge'
|
18
14
|
import SplitIcon from '@mui/icons-material/CallSplit'
|
19
15
|
import RestoreIcon from '@mui/icons-material/RestoreFromTrash'
|
20
|
-
import MoreVertIcon from '@mui/icons-material/MoreVert'
|
21
16
|
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'
|
22
17
|
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'
|
23
18
|
import CancelIcon from '@mui/icons-material/Cancel'
|
24
19
|
import StopIcon from '@mui/icons-material/Stop'
|
20
|
+
import HistoryIcon from '@mui/icons-material/History'
|
25
21
|
import { LyricsSegment, Word } from '../types'
|
26
22
|
import { useState, useEffect, useCallback } from 'react'
|
27
23
|
import TimelineEditor from './TimelineEditor'
|
28
24
|
import { nanoid } from 'nanoid'
|
25
|
+
import WordDivider from './WordDivider'
|
26
|
+
import useManualSync from '../hooks/useManualSync'
|
29
27
|
|
30
28
|
interface EditModalProps {
|
31
29
|
open: boolean
|
@@ -39,7 +37,9 @@ interface EditModalProps {
|
|
39
37
|
onDelete?: (segmentIndex: number) => void
|
40
38
|
onAddSegment?: (segmentIndex: number) => void
|
41
39
|
onSplitSegment?: (segmentIndex: number, afterWordIndex: number) => void
|
40
|
+
onMergeSegment?: (segmentIndex: number, mergeWithNext: boolean) => void
|
42
41
|
setModalSpacebarHandler: (handler: (() => (e: KeyboardEvent) => void) | undefined) => void
|
42
|
+
originalTranscribedSegment?: LyricsSegment | null
|
43
43
|
}
|
44
44
|
|
45
45
|
export default function EditModal({
|
@@ -54,18 +54,15 @@ export default function EditModal({
|
|
54
54
|
onDelete,
|
55
55
|
onAddSegment,
|
56
56
|
onSplitSegment,
|
57
|
+
onMergeSegment,
|
57
58
|
setModalSpacebarHandler,
|
59
|
+
originalTranscribedSegment
|
58
60
|
}: EditModalProps) {
|
59
|
-
// All useState hooks
|
60
61
|
const [editedSegment, setEditedSegment] = useState<LyricsSegment | null>(segment)
|
61
|
-
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null)
|
62
|
-
const [selectedWordIndex, setSelectedWordIndex] = useState<number | null>(null)
|
63
62
|
const [replacementText, setReplacementText] = useState('')
|
64
|
-
const [isManualSyncing, setIsManualSyncing] = useState(false)
|
65
|
-
const [syncWordIndex, setSyncWordIndex] = useState<number>(-1)
|
66
63
|
const [isPlaying, setIsPlaying] = useState(false)
|
67
64
|
|
68
|
-
// Define updateSegment first since
|
65
|
+
// Define updateSegment first since the hook depends on it
|
69
66
|
const updateSegment = useCallback((newWords: Word[]) => {
|
70
67
|
if (!editedSegment) return;
|
71
68
|
|
@@ -84,84 +81,78 @@ export default function EditModal({
|
|
84
81
|
})
|
85
82
|
}, [editedSegment])
|
86
83
|
|
87
|
-
//
|
88
|
-
const
|
89
|
-
|
90
|
-
|
91
|
-
|
84
|
+
// Use the new hook
|
85
|
+
const {
|
86
|
+
isManualSyncing,
|
87
|
+
syncWordIndex,
|
88
|
+
startManualSync,
|
89
|
+
cleanupManualSync,
|
90
|
+
handleSpacebar,
|
91
|
+
isSpacebarPressed
|
92
|
+
} = useManualSync({
|
93
|
+
editedSegment,
|
94
|
+
currentTime,
|
95
|
+
onPlaySegment,
|
96
|
+
updateSegment
|
97
|
+
})
|
92
98
|
|
93
99
|
const handleClose = useCallback(() => {
|
94
100
|
cleanupManualSync()
|
95
101
|
onClose()
|
96
102
|
}, [onClose, cleanupManualSync])
|
97
103
|
|
98
|
-
// All useEffect hooks
|
99
|
-
useEffect(() => {
|
100
|
-
setEditedSegment(segment)
|
101
|
-
}, [segment])
|
102
|
-
|
103
104
|
// Update the spacebar handler when modal state changes
|
104
105
|
useEffect(() => {
|
106
|
+
const spacebarHandler = handleSpacebar // Capture the current handler
|
107
|
+
|
105
108
|
if (open) {
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if (isManualSyncing && editedSegment) {
|
111
|
-
// Handle manual sync mode
|
112
|
-
if (syncWordIndex < editedSegment.words.length) {
|
113
|
-
const newWords = [...editedSegment.words]
|
114
|
-
const currentWord = newWords[syncWordIndex]
|
115
|
-
const prevWord = syncWordIndex > 0 ? newWords[syncWordIndex - 1] : null
|
116
|
-
|
117
|
-
currentWord.start_time = currentTime
|
118
|
-
|
119
|
-
if (prevWord) {
|
120
|
-
prevWord.end_time = currentTime - 0.01
|
121
|
-
}
|
122
|
-
|
123
|
-
if (syncWordIndex === editedSegment.words.length - 1) {
|
124
|
-
currentWord.end_time = editedSegment.end_time
|
125
|
-
setIsManualSyncing(false)
|
126
|
-
setSyncWordIndex(-1)
|
127
|
-
updateSegment(newWords)
|
128
|
-
} else {
|
129
|
-
setSyncWordIndex(syncWordIndex + 1)
|
130
|
-
updateSegment(newWords)
|
131
|
-
}
|
132
|
-
}
|
133
|
-
} else if (editedSegment && onPlaySegment) {
|
134
|
-
// Toggle segment playback when not in manual sync mode
|
135
|
-
const startTime = editedSegment.start_time ?? 0
|
136
|
-
const endTime = editedSegment.end_time ?? 0
|
137
|
-
|
138
|
-
if (currentTime >= startTime && currentTime <= endTime) {
|
139
|
-
if (window.toggleAudioPlayback) {
|
140
|
-
window.toggleAudioPlayback()
|
141
|
-
}
|
142
|
-
} else {
|
143
|
-
onPlaySegment(startTime)
|
144
|
-
}
|
145
|
-
}
|
109
|
+
console.log('EditModal - Setting up modal spacebar handler', {
|
110
|
+
hasPlaySegment: !!onPlaySegment,
|
111
|
+
editedSegmentId: editedSegment?.id,
|
112
|
+
handlerFunction: spacebarHandler.toString().slice(0, 100)
|
146
113
|
})
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
114
|
+
|
115
|
+
// Create a function that will be called by the global event listeners
|
116
|
+
const handleKeyEvent = (e: KeyboardEvent) => {
|
117
|
+
if (e.code === 'Space') {
|
118
|
+
spacebarHandler(e)
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
setModalSpacebarHandler(() => handleKeyEvent)
|
123
|
+
|
124
|
+
// Only cleanup when the effect is re-run or the modal is closed
|
125
|
+
return () => {
|
126
|
+
if (!open) {
|
127
|
+
console.log('EditModal - Cleanup: clearing modal spacebar handler')
|
128
|
+
setModalSpacebarHandler(undefined)
|
129
|
+
}
|
130
|
+
}
|
153
131
|
}
|
154
132
|
}, [
|
155
133
|
open,
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
onPlaySegment,
|
161
|
-
updateSegment,
|
162
|
-
setModalSpacebarHandler
|
134
|
+
handleSpacebar,
|
135
|
+
setModalSpacebarHandler,
|
136
|
+
editedSegment?.id,
|
137
|
+
onPlaySegment
|
163
138
|
])
|
164
139
|
|
140
|
+
// Update isPlaying when currentTime changes
|
141
|
+
useEffect(() => {
|
142
|
+
if (editedSegment) {
|
143
|
+
const startTime = editedSegment.start_time ?? 0
|
144
|
+
const endTime = editedSegment.end_time ?? 0
|
145
|
+
const isWithinSegment = currentTime >= startTime && currentTime <= endTime
|
146
|
+
|
147
|
+
setIsPlaying(isWithinSegment && window.isAudioPlaying === true)
|
148
|
+
}
|
149
|
+
}, [currentTime, editedSegment])
|
150
|
+
|
151
|
+
// All useEffect hooks
|
152
|
+
useEffect(() => {
|
153
|
+
setEditedSegment(segment)
|
154
|
+
}, [segment])
|
155
|
+
|
165
156
|
// Auto-stop sync if we go past the end time
|
166
157
|
useEffect(() => {
|
167
158
|
if (!editedSegment) return
|
@@ -171,23 +162,10 @@ export default function EditModal({
|
|
171
162
|
if (window.isAudioPlaying && currentTime > endTime) {
|
172
163
|
console.log('Stopping playback: current time exceeded end time')
|
173
164
|
window.toggleAudioPlayback?.()
|
174
|
-
|
175
|
-
setSyncWordIndex(-1)
|
165
|
+
cleanupManualSync()
|
176
166
|
}
|
177
167
|
|
178
|
-
}, [isManualSyncing, editedSegment, currentTime,
|
179
|
-
|
180
|
-
// Update isPlaying when currentTime changes
|
181
|
-
useEffect(() => {
|
182
|
-
if (editedSegment) {
|
183
|
-
const startTime = editedSegment.start_time ?? 0
|
184
|
-
const endTime = editedSegment.end_time ?? 0
|
185
|
-
const isWithinSegment = currentTime >= startTime && currentTime <= endTime
|
186
|
-
|
187
|
-
// Only consider it playing if it's within the segment AND audio is actually playing
|
188
|
-
setIsPlaying(isWithinSegment && window.isAudioPlaying === true)
|
189
|
-
}
|
190
|
-
}, [currentTime, editedSegment])
|
168
|
+
}, [isManualSyncing, editedSegment, currentTime, cleanupManualSync])
|
191
169
|
|
192
170
|
// Add a function to get safe time values
|
193
171
|
const getSafeTimeRange = (segment: LyricsSegment | null) => {
|
@@ -256,36 +234,36 @@ export default function EditModal({
|
|
256
234
|
const word = editedSegment.words[index]
|
257
235
|
const startTime = word.start_time ?? 0
|
258
236
|
const endTime = word.end_time ?? startTime + 0.5
|
259
|
-
const
|
260
|
-
|
237
|
+
const totalDuration = endTime - startTime
|
238
|
+
|
239
|
+
// Split on any number of spaces and filter out empty strings
|
240
|
+
const words = word.text.split(/\s+/).filter(w => w.length > 0)
|
261
241
|
|
262
242
|
if (words.length <= 1) {
|
263
|
-
//
|
243
|
+
// If no spaces found, split the word in half as before
|
264
244
|
const firstHalf = word.text.slice(0, Math.ceil(word.text.length / 2))
|
265
245
|
const secondHalf = word.text.slice(Math.ceil(word.text.length / 2))
|
266
246
|
words[0] = firstHalf
|
267
247
|
words[1] = secondHalf
|
268
248
|
}
|
269
249
|
|
270
|
-
|
271
|
-
|
272
|
-
{
|
273
|
-
id: nanoid(),
|
274
|
-
text: words[0],
|
275
|
-
start_time: startTime,
|
276
|
-
end_time: midTime,
|
277
|
-
confidence: 1.0
|
278
|
-
},
|
279
|
-
{
|
280
|
-
id: nanoid(),
|
281
|
-
text: words[1],
|
282
|
-
start_time: midTime,
|
283
|
-
end_time: endTime,
|
284
|
-
confidence: 1.0
|
285
|
-
}
|
286
|
-
)
|
250
|
+
// Calculate time per word
|
251
|
+
const timePerWord = totalDuration / words.length
|
287
252
|
|
288
|
-
|
253
|
+
// Create new word objects with evenly distributed times
|
254
|
+
const newWords = words.map((text, i) => ({
|
255
|
+
id: nanoid(),
|
256
|
+
text,
|
257
|
+
start_time: startTime + (i * timePerWord),
|
258
|
+
end_time: startTime + ((i + 1) * timePerWord),
|
259
|
+
confidence: 1.0
|
260
|
+
}))
|
261
|
+
|
262
|
+
// Replace the original word with the new words
|
263
|
+
const allWords = [...editedSegment.words]
|
264
|
+
allWords.splice(index, 1, ...newWords)
|
265
|
+
|
266
|
+
updateSegment(allWords)
|
289
267
|
}
|
290
268
|
|
291
269
|
const handleMergeWords = (index: number) => {
|
@@ -315,14 +293,10 @@ export default function EditModal({
|
|
315
293
|
setEditedSegment(JSON.parse(JSON.stringify(originalSegment)))
|
316
294
|
}
|
317
295
|
|
318
|
-
const
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
const handleMenuClose = () => {
|
324
|
-
setMenuAnchorEl(null)
|
325
|
-
setSelectedWordIndex(null)
|
296
|
+
const handleRevertToOriginal = () => {
|
297
|
+
if (originalTranscribedSegment) {
|
298
|
+
setEditedSegment(JSON.parse(JSON.stringify(originalTranscribedSegment)))
|
299
|
+
}
|
326
300
|
}
|
327
301
|
|
328
302
|
const handleSave = () => {
|
@@ -395,21 +369,12 @@ export default function EditModal({
|
|
395
369
|
}
|
396
370
|
}
|
397
371
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
return
|
372
|
+
const handleMergeSegment = (mergeWithNext: boolean) => {
|
373
|
+
if (segmentIndex !== null && editedSegment) {
|
374
|
+
handleSave() // Save current changes first
|
375
|
+
onMergeSegment?.(segmentIndex, mergeWithNext)
|
376
|
+
onClose()
|
404
377
|
}
|
405
|
-
|
406
|
-
if (!editedSegment || !onPlaySegment) return
|
407
|
-
|
408
|
-
setIsManualSyncing(true)
|
409
|
-
setSyncWordIndex(0)
|
410
|
-
// Start playing 3 seconds before segment start
|
411
|
-
const startTime = (editedSegment.start_time ?? 0) - 3
|
412
|
-
onPlaySegment(startTime)
|
413
378
|
}
|
414
379
|
|
415
380
|
// Handle play/stop button click
|
@@ -434,6 +399,12 @@ export default function EditModal({
|
|
434
399
|
maxWidth="md"
|
435
400
|
fullWidth
|
436
401
|
onKeyDown={handleKeyDown}
|
402
|
+
PaperProps={{
|
403
|
+
sx: {
|
404
|
+
height: '90vh', // Take up 90% of viewport height
|
405
|
+
margin: '5vh 0' // Add 5vh margin top and bottom
|
406
|
+
}
|
407
|
+
}}
|
437
408
|
>
|
438
409
|
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
439
410
|
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
@@ -456,8 +427,16 @@ export default function EditModal({
|
|
456
427
|
<CloseIcon />
|
457
428
|
</IconButton>
|
458
429
|
</DialogTitle>
|
459
|
-
<DialogContent
|
460
|
-
|
430
|
+
<DialogContent
|
431
|
+
dividers
|
432
|
+
sx={{
|
433
|
+
display: 'flex',
|
434
|
+
flexDirection: 'column',
|
435
|
+
flexGrow: 1, // Allow content to fill available space
|
436
|
+
overflow: 'hidden' // Prevent double scrollbars
|
437
|
+
}}
|
438
|
+
>
|
439
|
+
<Box sx={{ mb: 0 }}>
|
461
440
|
<TimelineEditor
|
462
441
|
words={editedSegment.words}
|
463
442
|
startTime={timeRange.start}
|
@@ -468,11 +447,11 @@ export default function EditModal({
|
|
468
447
|
/>
|
469
448
|
</Box>
|
470
449
|
|
471
|
-
<Box sx={{ mb:
|
450
|
+
<Box sx={{ mb: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
472
451
|
<Typography variant="body2" color="text.secondary">
|
473
|
-
Original Time Range: {originalSegment
|
452
|
+
Original Time Range: {originalSegment?.start_time?.toFixed(2) ?? 'N/A'} - {originalSegment?.end_time?.toFixed(2) ?? 'N/A'}
|
474
453
|
<br />
|
475
|
-
Current Time Range: {editedSegment
|
454
|
+
Current Time Range: {editedSegment?.start_time?.toFixed(2) ?? 'N/A'} - {editedSegment?.end_time?.toFixed(2) ?? 'N/A'}
|
476
455
|
</Typography>
|
477
456
|
|
478
457
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
@@ -486,69 +465,126 @@ export default function EditModal({
|
|
486
465
|
{isManualSyncing ? "Cancel Sync" : "Manual Sync"}
|
487
466
|
</Button>
|
488
467
|
{isManualSyncing && (
|
489
|
-
<
|
490
|
-
|
491
|
-
|
468
|
+
<Box>
|
469
|
+
<Typography variant="body2">
|
470
|
+
Word {syncWordIndex + 1} of {editedSegment?.words.length}: <strong>{editedSegment?.words[syncWordIndex]?.text || ''}</strong>
|
471
|
+
</Typography>
|
472
|
+
<Typography variant="caption" color="text.secondary">
|
473
|
+
{isSpacebarPressed ?
|
474
|
+
"Holding spacebar... Release when word ends" :
|
475
|
+
"Press spacebar when word starts (tap for short words, hold for long words)"}
|
476
|
+
</Typography>
|
477
|
+
</Box>
|
492
478
|
)}
|
493
479
|
</Box>
|
494
480
|
</Box>
|
495
481
|
|
496
|
-
<Box sx={{
|
482
|
+
<Box sx={{
|
483
|
+
display: 'flex',
|
484
|
+
flexDirection: 'column',
|
485
|
+
gap: 0.5,
|
486
|
+
mb: 3,
|
487
|
+
pt: 1,
|
488
|
+
flexGrow: 1,
|
489
|
+
overflowY: 'auto',
|
490
|
+
'&::-webkit-scrollbar': {
|
491
|
+
display: 'none' // Hide scrollbar for WebKit browsers (Chrome, Safari, etc.)
|
492
|
+
},
|
493
|
+
msOverflowStyle: 'none', // Hide scrollbar for IE and Edge
|
494
|
+
scrollbarWidth: 'none', // Hide scrollbar for Firefox
|
495
|
+
}}>
|
496
|
+
{/* Initial divider with Add Segment Before button */}
|
497
|
+
<WordDivider
|
498
|
+
onAddWord={() => handleAddWord(-1)}
|
499
|
+
onAddSegmentBefore={() => onAddSegment?.(segmentIndex)}
|
500
|
+
onMergeSegment={() => handleMergeSegment(false)}
|
501
|
+
isFirst={true}
|
502
|
+
sx={{ ml: 15 }}
|
503
|
+
/>
|
504
|
+
|
497
505
|
{editedSegment.words.map((word, index) => (
|
498
|
-
<Box key={
|
499
|
-
<
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
506
|
+
<Box key={word.id}>
|
507
|
+
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
508
|
+
<TextField
|
509
|
+
label={`Word ${index}`}
|
510
|
+
value={word.text}
|
511
|
+
onChange={(e) => handleWordChange(index, { text: e.target.value })}
|
512
|
+
fullWidth
|
513
|
+
size="small"
|
514
|
+
/>
|
515
|
+
<TextField
|
516
|
+
label="Start Time"
|
517
|
+
value={word.start_time?.toFixed(2) ?? ''}
|
518
|
+
onChange={(e) => handleWordChange(index, { start_time: parseFloat(e.target.value) })}
|
519
|
+
type="number"
|
520
|
+
inputProps={{ step: 0.01 }}
|
521
|
+
sx={{ width: '150px' }}
|
522
|
+
size="small"
|
523
|
+
/>
|
524
|
+
<TextField
|
525
|
+
label="End Time"
|
526
|
+
value={word.end_time?.toFixed(2) ?? ''}
|
527
|
+
onChange={(e) => handleWordChange(index, { end_time: parseFloat(e.target.value) })}
|
528
|
+
type="number"
|
529
|
+
inputProps={{ step: 0.01 }}
|
530
|
+
sx={{ width: '150px' }}
|
531
|
+
size="small"
|
532
|
+
/>
|
533
|
+
<IconButton
|
534
|
+
onClick={() => handleSplitWord(index)}
|
535
|
+
title="Split Word"
|
536
|
+
sx={{ color: 'primary.main' }}
|
537
|
+
size="small"
|
538
|
+
>
|
539
|
+
<SplitIcon fontSize="small" />
|
540
|
+
</IconButton>
|
541
|
+
<IconButton
|
542
|
+
onClick={() => handleRemoveWord(index)}
|
543
|
+
disabled={editedSegment.words.length <= 1}
|
544
|
+
title="Remove Word"
|
545
|
+
sx={{ color: 'error.main' }}
|
546
|
+
size="small"
|
547
|
+
>
|
548
|
+
<DeleteIcon fontSize="small" />
|
549
|
+
</IconButton>
|
550
|
+
</Box>
|
551
|
+
|
552
|
+
{/* Update the WordDivider usage to include split segment */}
|
553
|
+
<WordDivider
|
554
|
+
onAddWord={() => handleAddWord(index)}
|
555
|
+
onMergeWords={() => handleMergeWords(index)}
|
556
|
+
onSplitSegment={() => handleSplitSegment(index)}
|
557
|
+
onAddSegmentAfter={
|
558
|
+
index === editedSegment.words.length - 1
|
559
|
+
? () => onAddSegment?.(segmentIndex + 1)
|
560
|
+
: undefined
|
561
|
+
}
|
562
|
+
onMergeSegment={
|
563
|
+
index === editedSegment.words.length - 1
|
564
|
+
? () => handleMergeSegment(true)
|
565
|
+
: undefined
|
566
|
+
}
|
567
|
+
canMerge={index < editedSegment.words.length - 1}
|
568
|
+
isLast={index === editedSegment.words.length - 1}
|
569
|
+
sx={{ ml: 15 }}
|
523
570
|
/>
|
524
|
-
<IconButton
|
525
|
-
onClick={() => handleRemoveWord(index)}
|
526
|
-
disabled={editedSegment.words.length <= 1}
|
527
|
-
sx={{ color: 'error.main' }}
|
528
|
-
>
|
529
|
-
<DeleteIcon fontSize="small" />
|
530
|
-
</IconButton>
|
531
|
-
<IconButton onClick={(e) => handleWordMenu(e, index)}>
|
532
|
-
<MoreVertIcon />
|
533
|
-
</IconButton>
|
534
571
|
</Box>
|
535
572
|
))}
|
536
573
|
</Box>
|
537
574
|
|
538
|
-
<Box sx={{ display: 'flex', gap: 2 }}>
|
575
|
+
<Box sx={{ display: 'flex', gap: 2, mt: 2 }}>
|
539
576
|
<TextField
|
540
|
-
label="Replace all words"
|
541
577
|
value={replacementText}
|
542
578
|
onChange={(e) => setReplacementText(e.target.value)}
|
543
|
-
|
544
|
-
placeholder="Type or paste replacement words here"
|
579
|
+
placeholder="Replace all words"
|
545
580
|
size="small"
|
581
|
+
sx={{ flexGrow: 1, maxWidth: 'calc(100% - 140px)' }} // Reserve space for the button
|
546
582
|
/>
|
547
583
|
<Button
|
548
|
-
variant="contained"
|
549
|
-
startIcon={<AutoFixHighIcon />}
|
550
584
|
onClick={handleReplaceAllWords}
|
551
|
-
|
585
|
+
startIcon={<AutoFixHighIcon />}
|
586
|
+
size="small"
|
587
|
+
sx={{ whiteSpace: 'nowrap' }}
|
552
588
|
>
|
553
589
|
Replace All
|
554
590
|
</Button>
|
@@ -562,14 +598,15 @@ export default function EditModal({
|
|
562
598
|
>
|
563
599
|
Reset
|
564
600
|
</Button>
|
565
|
-
|
601
|
+
{originalTranscribedSegment && (
|
566
602
|
<Button
|
567
|
-
|
568
|
-
|
569
|
-
color="primary"
|
603
|
+
onClick={handleRevertToOriginal}
|
604
|
+
startIcon={<HistoryIcon />}
|
570
605
|
>
|
571
|
-
|
606
|
+
Un-Correct
|
572
607
|
</Button>
|
608
|
+
)}
|
609
|
+
<Box sx={{ mr: 'auto' }}>
|
573
610
|
<Button
|
574
611
|
startIcon={<DeleteIcon />}
|
575
612
|
onClick={handleDelete}
|
@@ -579,56 +616,14 @@ export default function EditModal({
|
|
579
616
|
</Button>
|
580
617
|
</Box>
|
581
618
|
<Button onClick={handleClose}>Cancel</Button>
|
582
|
-
<Button
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
619
|
+
<Button
|
620
|
+
onClick={handleSave}
|
621
|
+
variant="contained"
|
622
|
+
disabled={!editedSegment || editedSegment.words.length === 0}
|
623
|
+
>
|
624
|
+
Save
|
587
625
|
</Button>
|
588
626
|
</DialogActions>
|
589
|
-
|
590
|
-
<Menu
|
591
|
-
anchorEl={menuAnchorEl}
|
592
|
-
open={Boolean(menuAnchorEl)}
|
593
|
-
onClose={handleMenuClose}
|
594
|
-
>
|
595
|
-
<MenuItem onClick={() => {
|
596
|
-
handleAddWord(selectedWordIndex!)
|
597
|
-
handleMenuClose()
|
598
|
-
}}>
|
599
|
-
<AddIcon sx={{ mr: 1 }} /> Add Word After
|
600
|
-
</MenuItem>
|
601
|
-
<MenuItem onClick={() => {
|
602
|
-
handleSplitWord(selectedWordIndex!)
|
603
|
-
handleMenuClose()
|
604
|
-
}}>
|
605
|
-
<SplitIcon sx={{ mr: 1 }} /> Split Word
|
606
|
-
</MenuItem>
|
607
|
-
<MenuItem onClick={() => {
|
608
|
-
handleSplitSegment(selectedWordIndex!)
|
609
|
-
handleMenuClose()
|
610
|
-
}}>
|
611
|
-
<SplitIcon sx={{ mr: 1 }} /> Split Segment After Word
|
612
|
-
</MenuItem>
|
613
|
-
<MenuItem
|
614
|
-
onClick={() => {
|
615
|
-
handleMergeWords(selectedWordIndex!)
|
616
|
-
handleMenuClose()
|
617
|
-
}}
|
618
|
-
disabled={selectedWordIndex === editedSegment.words.length - 1}
|
619
|
-
>
|
620
|
-
<MergeIcon sx={{ mr: 1 }} /> Merge with Next
|
621
|
-
</MenuItem>
|
622
|
-
<MenuItem
|
623
|
-
onClick={() => {
|
624
|
-
handleRemoveWord(selectedWordIndex!)
|
625
|
-
handleMenuClose()
|
626
|
-
}}
|
627
|
-
disabled={editedSegment.words.length <= 1}
|
628
|
-
>
|
629
|
-
<DeleteIcon sx={{ mr: 1 }} color="error" /> Remove
|
630
|
-
</MenuItem>
|
631
|
-
</Menu>
|
632
627
|
</Dialog>
|
633
628
|
)
|
634
629
|
}
|