lyrics-transcriber 0.41.0__py3-none-any.whl → 0.43.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 +30 -52
- lyrics_transcriber/correction/anchor_sequence.py +325 -150
- lyrics_transcriber/correction/corrector.py +224 -107
- lyrics_transcriber/correction/handlers/base.py +28 -10
- lyrics_transcriber/correction/handlers/extend_anchor.py +47 -24
- lyrics_transcriber/correction/handlers/levenshtein.py +75 -33
- lyrics_transcriber/correction/handlers/llm.py +290 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +81 -36
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +46 -26
- lyrics_transcriber/correction/handlers/repeat.py +28 -11
- lyrics_transcriber/correction/handlers/sound_alike.py +68 -32
- lyrics_transcriber/correction/handlers/syllables_match.py +80 -30
- lyrics_transcriber/correction/handlers/word_count_match.py +36 -19
- lyrics_transcriber/correction/handlers/word_operations.py +68 -22
- lyrics_transcriber/correction/text_utils.py +3 -7
- lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.6.0.cjs +934 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/dist/assets/{index-DKnNJHRK.js → index-D0Gr3Ep7.js} +16509 -9038
- lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/package.json +6 -2
- lyrics_transcriber/frontend/src/App.tsx +18 -2
- lyrics_transcriber/frontend/src/api.ts +103 -6
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +14 -6
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +86 -59
- lyrics_transcriber/frontend/src/components/EditModal.tsx +281 -63
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +2 -2
- lyrics_transcriber/frontend/src/components/Header.tsx +249 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +320 -266
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +120 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +174 -52
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +158 -114
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +59 -78
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +39 -16
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +4 -10
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +134 -68
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +1 -1
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +85 -115
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +15 -7
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +67 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +7 -7
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +121 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +70 -49
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +75 -12
- lyrics_transcriber/lyrics/file_provider.py +6 -5
- lyrics_transcriber/lyrics/genius.py +5 -2
- lyrics_transcriber/lyrics/spotify.py +58 -21
- lyrics_transcriber/output/ass/config.py +16 -5
- lyrics_transcriber/output/cdg.py +1 -1
- lyrics_transcriber/output/generator.py +22 -8
- lyrics_transcriber/output/plain_text.py +15 -10
- lyrics_transcriber/output/segment_resizer.py +16 -3
- lyrics_transcriber/output/subtitles.py +27 -1
- lyrics_transcriber/output/video.py +107 -1
- lyrics_transcriber/review/__init__.py +0 -1
- lyrics_transcriber/review/server.py +337 -164
- lyrics_transcriber/transcribers/audioshake.py +3 -0
- lyrics_transcriber/transcribers/base_transcriber.py +11 -3
- lyrics_transcriber/transcribers/whisper.py +11 -1
- lyrics_transcriber/types.py +151 -105
- lyrics_transcriber/utils/word_utils.py +27 -0
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/METADATA +3 -1
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/RECORD +75 -61
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/WHEEL +1 -1
- lyrics_transcriber/frontend/dist/assets/index-DKnNJHRK.js.map +0 -1
- lyrics_transcriber/frontend/package-lock.json +0 -4260
- lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +0 -202
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/entry_points.txt +0 -0
@@ -20,8 +20,10 @@ import RestoreIcon from '@mui/icons-material/RestoreFromTrash'
|
|
20
20
|
import MoreVertIcon from '@mui/icons-material/MoreVert'
|
21
21
|
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'
|
22
22
|
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'
|
23
|
+
import CancelIcon from '@mui/icons-material/Cancel'
|
24
|
+
import StopIcon from '@mui/icons-material/Stop'
|
23
25
|
import { LyricsSegment, Word } from '../types'
|
24
|
-
import { useState, useEffect } from 'react'
|
26
|
+
import { useState, useEffect, useCallback } from 'react'
|
25
27
|
import TimelineEditor from './TimelineEditor'
|
26
28
|
import { nanoid } from 'nanoid'
|
27
29
|
|
@@ -35,6 +37,9 @@ interface EditModalProps {
|
|
35
37
|
onPlaySegment?: (startTime: number) => void
|
36
38
|
currentTime?: number
|
37
39
|
onDelete?: (segmentIndex: number) => void
|
40
|
+
onAddSegment?: (segmentIndex: number) => void
|
41
|
+
onSplitSegment?: (segmentIndex: number, afterWordIndex: number) => void
|
42
|
+
setModalSpacebarHandler: (handler: (() => (e: KeyboardEvent) => void) | undefined) => void
|
38
43
|
}
|
39
44
|
|
40
45
|
export default function EditModal({
|
@@ -47,43 +52,166 @@ export default function EditModal({
|
|
47
52
|
onPlaySegment,
|
48
53
|
currentTime = 0,
|
49
54
|
onDelete,
|
55
|
+
onAddSegment,
|
56
|
+
onSplitSegment,
|
57
|
+
setModalSpacebarHandler,
|
50
58
|
}: EditModalProps) {
|
59
|
+
// All useState hooks
|
51
60
|
const [editedSegment, setEditedSegment] = useState<LyricsSegment | null>(segment)
|
52
61
|
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null)
|
53
62
|
const [selectedWordIndex, setSelectedWordIndex] = useState<number | null>(null)
|
54
63
|
const [replacementText, setReplacementText] = useState('')
|
64
|
+
const [isManualSyncing, setIsManualSyncing] = useState(false)
|
65
|
+
const [syncWordIndex, setSyncWordIndex] = useState<number>(-1)
|
66
|
+
const [isPlaying, setIsPlaying] = useState(false)
|
55
67
|
|
56
|
-
//
|
68
|
+
// Define updateSegment first since other hooks depend on it
|
69
|
+
const updateSegment = useCallback((newWords: Word[]) => {
|
70
|
+
if (!editedSegment) return;
|
71
|
+
|
72
|
+
const validStartTimes = newWords.map(w => w.start_time).filter((t): t is number => t !== null)
|
73
|
+
const validEndTimes = newWords.map(w => w.end_time).filter((t): t is number => t !== null)
|
74
|
+
|
75
|
+
const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null
|
76
|
+
const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null
|
77
|
+
|
78
|
+
setEditedSegment({
|
79
|
+
...editedSegment,
|
80
|
+
words: newWords,
|
81
|
+
text: newWords.map(w => w.text).join(' '),
|
82
|
+
start_time: segmentStartTime,
|
83
|
+
end_time: segmentEndTime
|
84
|
+
})
|
85
|
+
}, [editedSegment])
|
86
|
+
|
87
|
+
// Other useCallback hooks
|
88
|
+
const cleanupManualSync = useCallback(() => {
|
89
|
+
setIsManualSyncing(false)
|
90
|
+
setSyncWordIndex(-1)
|
91
|
+
}, [])
|
92
|
+
|
93
|
+
const handleClose = useCallback(() => {
|
94
|
+
cleanupManualSync()
|
95
|
+
onClose()
|
96
|
+
}, [onClose, cleanupManualSync])
|
97
|
+
|
98
|
+
// All useEffect hooks
|
57
99
|
useEffect(() => {
|
58
100
|
setEditedSegment(segment)
|
59
101
|
}, [segment])
|
60
102
|
|
103
|
+
// Update the spacebar handler when modal state changes
|
104
|
+
useEffect(() => {
|
105
|
+
if (open) {
|
106
|
+
setModalSpacebarHandler(() => (e: KeyboardEvent) => {
|
107
|
+
e.preventDefault()
|
108
|
+
e.stopPropagation()
|
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
|
+
}
|
146
|
+
})
|
147
|
+
} else {
|
148
|
+
setModalSpacebarHandler(undefined)
|
149
|
+
}
|
150
|
+
|
151
|
+
return () => {
|
152
|
+
setModalSpacebarHandler(undefined)
|
153
|
+
}
|
154
|
+
}, [
|
155
|
+
open,
|
156
|
+
isManualSyncing,
|
157
|
+
editedSegment,
|
158
|
+
syncWordIndex,
|
159
|
+
currentTime,
|
160
|
+
onPlaySegment,
|
161
|
+
updateSegment,
|
162
|
+
setModalSpacebarHandler
|
163
|
+
])
|
164
|
+
|
165
|
+
// Auto-stop sync if we go past the end time
|
166
|
+
useEffect(() => {
|
167
|
+
if (!editedSegment) return
|
168
|
+
|
169
|
+
const endTime = editedSegment.end_time ?? 0
|
170
|
+
|
171
|
+
if (window.isAudioPlaying && currentTime > endTime) {
|
172
|
+
console.log('Stopping playback: current time exceeded end time')
|
173
|
+
window.toggleAudioPlayback?.()
|
174
|
+
setIsManualSyncing(false)
|
175
|
+
setSyncWordIndex(-1)
|
176
|
+
}
|
177
|
+
|
178
|
+
}, [isManualSyncing, editedSegment, currentTime, setSyncWordIndex])
|
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])
|
191
|
+
|
192
|
+
// Add a function to get safe time values
|
193
|
+
const getSafeTimeRange = (segment: LyricsSegment | null) => {
|
194
|
+
if (!segment) return { start: 0, end: 1 }; // Default 1-second range
|
195
|
+
const start = segment.start_time ?? 0;
|
196
|
+
const end = segment.end_time ?? (start + 1);
|
197
|
+
return { start, end };
|
198
|
+
}
|
199
|
+
|
200
|
+
// Early return after all hooks and function definitions
|
61
201
|
if (!segment || segmentIndex === null || !editedSegment || !originalSegment) return null
|
62
202
|
|
63
|
-
|
203
|
+
// Get safe time values for TimelineEditor
|
204
|
+
const timeRange = getSafeTimeRange(editedSegment)
|
205
|
+
|
206
|
+
const handleWordChange = (index: number, updates: Partial<Word>) => {
|
64
207
|
const newWords = [...editedSegment.words]
|
65
208
|
newWords[index] = {
|
66
209
|
...newWords[index],
|
67
|
-
|
68
|
-
? parseFloat(Number(value).toFixed(4))
|
69
|
-
: value
|
210
|
+
...updates
|
70
211
|
}
|
71
212
|
updateSegment(newWords)
|
72
213
|
}
|
73
214
|
|
74
|
-
const updateSegment = (newWords: Word[]) => {
|
75
|
-
const segmentStartTime = Math.min(...newWords.map(w => w.start_time))
|
76
|
-
const segmentEndTime = Math.max(...newWords.map(w => w.end_time))
|
77
|
-
|
78
|
-
setEditedSegment({
|
79
|
-
...editedSegment,
|
80
|
-
words: newWords,
|
81
|
-
text: newWords.map(w => w.text).join(' '),
|
82
|
-
start_time: segmentStartTime,
|
83
|
-
end_time: segmentEndTime
|
84
|
-
})
|
85
|
-
}
|
86
|
-
|
87
215
|
const handleAddWord = (index?: number) => {
|
88
216
|
const newWords = [...editedSegment.words]
|
89
217
|
let newWord: Word
|
@@ -91,11 +219,12 @@ export default function EditModal({
|
|
91
219
|
if (index === undefined) {
|
92
220
|
// Add at end
|
93
221
|
const lastWord = newWords[newWords.length - 1]
|
222
|
+
const lastEndTime = lastWord.end_time ?? 0
|
94
223
|
newWord = {
|
95
224
|
id: nanoid(),
|
96
225
|
text: '',
|
97
|
-
start_time:
|
98
|
-
end_time:
|
226
|
+
start_time: lastEndTime,
|
227
|
+
end_time: lastEndTime + 0.5,
|
99
228
|
confidence: 1.0
|
100
229
|
}
|
101
230
|
newWords.push(newWord)
|
@@ -104,8 +233,11 @@ export default function EditModal({
|
|
104
233
|
const prevWord = newWords[index]
|
105
234
|
const nextWord = newWords[index + 1]
|
106
235
|
const midTime = prevWord ?
|
107
|
-
(nextWord ?
|
108
|
-
|
236
|
+
(nextWord ?
|
237
|
+
((prevWord.end_time ?? 0) + (nextWord.start_time ?? 0)) / 2 :
|
238
|
+
(prevWord.end_time ?? 0) + 0.5
|
239
|
+
) :
|
240
|
+
(nextWord ? (nextWord.start_time ?? 0) - 0.5 : 0)
|
109
241
|
|
110
242
|
newWord = {
|
111
243
|
id: nanoid(),
|
@@ -122,7 +254,9 @@ export default function EditModal({
|
|
122
254
|
|
123
255
|
const handleSplitWord = (index: number) => {
|
124
256
|
const word = editedSegment.words[index]
|
125
|
-
const
|
257
|
+
const startTime = word.start_time ?? 0
|
258
|
+
const endTime = word.end_time ?? startTime + 0.5
|
259
|
+
const midTime = (startTime + endTime) / 2
|
126
260
|
const words = word.text.split(/\s+/)
|
127
261
|
|
128
262
|
if (words.length <= 1) {
|
@@ -138,7 +272,7 @@ export default function EditModal({
|
|
138
272
|
{
|
139
273
|
id: nanoid(),
|
140
274
|
text: words[0],
|
141
|
-
start_time:
|
275
|
+
start_time: startTime,
|
142
276
|
end_time: midTime,
|
143
277
|
confidence: 1.0
|
144
278
|
},
|
@@ -146,7 +280,7 @@ export default function EditModal({
|
|
146
280
|
id: nanoid(),
|
147
281
|
text: words[1],
|
148
282
|
start_time: midTime,
|
149
|
-
end_time:
|
283
|
+
end_time: endTime,
|
150
284
|
confidence: 1.0
|
151
285
|
}
|
152
286
|
)
|
@@ -164,8 +298,8 @@ export default function EditModal({
|
|
164
298
|
newWords.splice(index, 2, {
|
165
299
|
id: nanoid(),
|
166
300
|
text: `${word1.text} ${word2.text}`.trim(),
|
167
|
-
start_time: word1.start_time,
|
168
|
-
end_time: word2.end_time,
|
301
|
+
start_time: word1.start_time ?? null,
|
302
|
+
end_time: word2.end_time ?? null,
|
169
303
|
confidence: 1.0
|
170
304
|
})
|
171
305
|
|
@@ -198,7 +332,7 @@ export default function EditModal({
|
|
198
332
|
originalText: segment?.text,
|
199
333
|
editedText: editedSegment.text,
|
200
334
|
wordCount: editedSegment.words.length,
|
201
|
-
timeRange: `${editedSegment.start_time
|
335
|
+
timeRange: `${editedSegment.start_time?.toFixed(4) ?? 'N/A'} - ${editedSegment.end_time?.toFixed(4) ?? 'N/A'}`
|
202
336
|
})
|
203
337
|
onSave(editedSegment)
|
204
338
|
onClose()
|
@@ -209,7 +343,9 @@ export default function EditModal({
|
|
209
343
|
if (!editedSegment) return
|
210
344
|
|
211
345
|
const newWords = replacementText.trim().split(/\s+/)
|
212
|
-
const
|
346
|
+
const startTime = editedSegment.start_time ?? 0
|
347
|
+
const endTime = editedSegment.end_time ?? (startTime + newWords.length) // Default to 1 second per word
|
348
|
+
const segmentDuration = endTime - startTime
|
213
349
|
|
214
350
|
let updatedWords: Word[]
|
215
351
|
|
@@ -228,8 +364,8 @@ export default function EditModal({
|
|
228
364
|
updatedWords = newWords.map((text, index) => ({
|
229
365
|
id: nanoid(), // Generate new ID
|
230
366
|
text,
|
231
|
-
start_time:
|
232
|
-
end_time:
|
367
|
+
start_time: startTime + (index * avgWordDuration),
|
368
|
+
end_time: startTime + ((index + 1) * avgWordDuration),
|
233
369
|
confidence: 1.0
|
234
370
|
}))
|
235
371
|
}
|
@@ -252,10 +388,49 @@ export default function EditModal({
|
|
252
388
|
}
|
253
389
|
}
|
254
390
|
|
391
|
+
const handleSplitSegment = (wordIndex: number) => {
|
392
|
+
if (segmentIndex !== null && editedSegment) {
|
393
|
+
handleSave() // Save current changes first
|
394
|
+
onSplitSegment?.(segmentIndex, wordIndex)
|
395
|
+
}
|
396
|
+
}
|
397
|
+
|
398
|
+
// Add this new function to handle manual sync
|
399
|
+
const startManualSync = () => {
|
400
|
+
if (isManualSyncing) {
|
401
|
+
setIsManualSyncing(false)
|
402
|
+
setSyncWordIndex(-1)
|
403
|
+
return
|
404
|
+
}
|
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
|
+
}
|
414
|
+
|
415
|
+
// Handle play/stop button click
|
416
|
+
const handlePlayButtonClick = () => {
|
417
|
+
if (!segment?.start_time || !onPlaySegment) return
|
418
|
+
|
419
|
+
if (isPlaying) {
|
420
|
+
// Stop playback
|
421
|
+
if (window.toggleAudioPlayback) {
|
422
|
+
window.toggleAudioPlayback()
|
423
|
+
}
|
424
|
+
} else {
|
425
|
+
// Start playback
|
426
|
+
onPlaySegment(segment.start_time)
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
255
430
|
return (
|
256
431
|
<Dialog
|
257
432
|
open={open}
|
258
|
-
onClose={
|
433
|
+
onClose={handleClose}
|
259
434
|
maxWidth="md"
|
260
435
|
fullWidth
|
261
436
|
onKeyDown={handleKeyDown}
|
@@ -263,13 +438,17 @@ export default function EditModal({
|
|
263
438
|
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
264
439
|
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
265
440
|
Edit Segment {segmentIndex}
|
266
|
-
{segment?.start_time !==
|
441
|
+
{segment?.start_time !== null && onPlaySegment && (
|
267
442
|
<IconButton
|
268
443
|
size="small"
|
269
|
-
onClick={
|
444
|
+
onClick={handlePlayButtonClick}
|
270
445
|
sx={{ padding: '4px' }}
|
271
446
|
>
|
272
|
-
|
447
|
+
{isPlaying ? (
|
448
|
+
<StopIcon />
|
449
|
+
) : (
|
450
|
+
<PlayCircleOutlineIcon />
|
451
|
+
)}
|
273
452
|
</IconButton>
|
274
453
|
)}
|
275
454
|
</Box>
|
@@ -281,23 +460,38 @@ export default function EditModal({
|
|
281
460
|
<Box sx={{ mb: 2 }}>
|
282
461
|
<TimelineEditor
|
283
462
|
words={editedSegment.words}
|
284
|
-
startTime={
|
285
|
-
endTime={
|
286
|
-
onWordUpdate={
|
287
|
-
const newWords = [...editedSegment.words]
|
288
|
-
newWords[index] = { ...newWords[index], ...updates }
|
289
|
-
updateSegment(newWords)
|
290
|
-
}}
|
463
|
+
startTime={timeRange.start}
|
464
|
+
endTime={timeRange.end}
|
465
|
+
onWordUpdate={handleWordChange}
|
291
466
|
currentTime={currentTime}
|
292
467
|
onPlaySegment={onPlaySegment}
|
293
468
|
/>
|
294
469
|
</Box>
|
295
470
|
|
296
|
-
<
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
471
|
+
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
472
|
+
<Typography variant="body2" color="text.secondary">
|
473
|
+
Original Time Range: {originalSegment.start_time?.toFixed(2) ?? 'N/A'} - {originalSegment.end_time?.toFixed(2) ?? 'N/A'}
|
474
|
+
<br />
|
475
|
+
Current Time Range: {editedSegment.start_time?.toFixed(2) ?? 'N/A'} - {editedSegment.end_time?.toFixed(2) ?? 'N/A'}
|
476
|
+
</Typography>
|
477
|
+
|
478
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
479
|
+
<Button
|
480
|
+
variant={isManualSyncing ? "outlined" : "contained"}
|
481
|
+
onClick={startManualSync}
|
482
|
+
disabled={!onPlaySegment}
|
483
|
+
startIcon={isManualSyncing ? <CancelIcon /> : <PlayCircleOutlineIcon />}
|
484
|
+
color={isManualSyncing ? "error" : "primary"}
|
485
|
+
>
|
486
|
+
{isManualSyncing ? "Cancel Sync" : "Manual Sync"}
|
487
|
+
</Button>
|
488
|
+
{isManualSyncing && (
|
489
|
+
<Typography variant="body2">
|
490
|
+
Press spacebar for word {syncWordIndex + 1} of {editedSegment?.words.length}
|
491
|
+
</Typography>
|
492
|
+
)}
|
493
|
+
</Box>
|
494
|
+
</Box>
|
301
495
|
|
302
496
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 3 }}>
|
303
497
|
{editedSegment.words.map((word, index) => (
|
@@ -305,14 +499,14 @@ export default function EditModal({
|
|
305
499
|
<TextField
|
306
500
|
label={`Word ${index}`}
|
307
501
|
value={word.text}
|
308
|
-
onChange={(e) => handleWordChange(index,
|
502
|
+
onChange={(e) => handleWordChange(index, { text: e.target.value })}
|
309
503
|
fullWidth
|
310
504
|
size="small"
|
311
505
|
/>
|
312
506
|
<TextField
|
313
507
|
label="Start Time"
|
314
|
-
value={word.start_time
|
315
|
-
onChange={(e) => handleWordChange(index,
|
508
|
+
value={word.start_time?.toFixed(2) ?? ''}
|
509
|
+
onChange={(e) => handleWordChange(index, { start_time: parseFloat(e.target.value) })}
|
316
510
|
type="number"
|
317
511
|
inputProps={{ step: 0.01 }}
|
318
512
|
sx={{ width: '150px' }}
|
@@ -320,13 +514,20 @@ export default function EditModal({
|
|
320
514
|
/>
|
321
515
|
<TextField
|
322
516
|
label="End Time"
|
323
|
-
value={word.end_time
|
324
|
-
onChange={(e) => handleWordChange(index,
|
517
|
+
value={word.end_time?.toFixed(2) ?? ''}
|
518
|
+
onChange={(e) => handleWordChange(index, { end_time: parseFloat(e.target.value) })}
|
325
519
|
type="number"
|
326
520
|
inputProps={{ step: 0.01 }}
|
327
521
|
sx={{ width: '150px' }}
|
328
522
|
size="small"
|
329
523
|
/>
|
524
|
+
<IconButton
|
525
|
+
onClick={() => handleRemoveWord(index)}
|
526
|
+
disabled={editedSegment.words.length <= 1}
|
527
|
+
sx={{ color: 'error.main' }}
|
528
|
+
>
|
529
|
+
<DeleteIcon fontSize="small" />
|
530
|
+
</IconButton>
|
330
531
|
<IconButton onClick={(e) => handleWordMenu(e, index)}>
|
331
532
|
<MoreVertIcon />
|
332
533
|
</IconButton>
|
@@ -361,16 +562,27 @@ export default function EditModal({
|
|
361
562
|
>
|
362
563
|
Reset
|
363
564
|
</Button>
|
364
|
-
<
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
565
|
+
<Box sx={{ mr: 'auto', display: 'flex', gap: 1 }}>
|
566
|
+
<Button
|
567
|
+
startIcon={<AddIcon />}
|
568
|
+
onClick={() => segmentIndex !== null && onAddSegment?.(segmentIndex)}
|
569
|
+
color="primary"
|
570
|
+
>
|
571
|
+
Add Segment Before
|
572
|
+
</Button>
|
573
|
+
<Button
|
574
|
+
startIcon={<DeleteIcon />}
|
575
|
+
onClick={handleDelete}
|
576
|
+
color="error"
|
577
|
+
>
|
578
|
+
Delete Segment
|
579
|
+
</Button>
|
580
|
+
</Box>
|
581
|
+
<Button onClick={handleClose}>Cancel</Button>
|
582
|
+
<Button onClick={() => {
|
583
|
+
cleanupManualSync()
|
584
|
+
onSave(editedSegment)
|
585
|
+
}}>
|
374
586
|
Save Changes
|
375
587
|
</Button>
|
376
588
|
</DialogActions>
|
@@ -392,6 +604,12 @@ export default function EditModal({
|
|
392
604
|
}}>
|
393
605
|
<SplitIcon sx={{ mr: 1 }} /> Split Word
|
394
606
|
</MenuItem>
|
607
|
+
<MenuItem onClick={() => {
|
608
|
+
handleSplitSegment(selectedWordIndex!)
|
609
|
+
handleMenuClose()
|
610
|
+
}}>
|
611
|
+
<SplitIcon sx={{ mr: 1 }} /> Split Segment After Word
|
612
|
+
</MenuItem>
|
395
613
|
<MenuItem
|
396
614
|
onClick={() => {
|
397
615
|
handleMergeWords(selectedWordIndex!)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import { ChangeEvent, DragEvent, useState } from 'react'
|
2
2
|
import { Paper, Typography } from '@mui/material'
|
3
3
|
import CloudUploadIcon from '@mui/icons-material/CloudUpload'
|
4
|
-
import {
|
4
|
+
import { CorrectionData } from '../types'
|
5
5
|
|
6
6
|
interface FileUploadProps {
|
7
|
-
onUpload: (data:
|
7
|
+
onUpload: (data: CorrectionData) => void
|
8
8
|
}
|
9
9
|
|
10
10
|
export default function FileUpload({ onUpload }: FileUploadProps) {
|