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
@@ -1,3 +1,4 @@
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react'
|
1
2
|
import {
|
2
3
|
AnchorSequence,
|
3
4
|
CorrectionData,
|
@@ -7,9 +8,7 @@ import {
|
|
7
8
|
LyricsSegment
|
8
9
|
} from '../types'
|
9
10
|
import { Box, Button, Grid, useMediaQuery, useTheme } from '@mui/material'
|
10
|
-
import { useCallback, useState, useEffect } from 'react'
|
11
11
|
import { ApiClient } from '../api'
|
12
|
-
import DetailsModal from './DetailsModal'
|
13
12
|
import ReferenceView from './ReferenceView'
|
14
13
|
import TranscriptionView from './TranscriptionView'
|
15
14
|
import { WordClickInfo, FlashType } from './shared/types'
|
@@ -19,12 +18,18 @@ import {
|
|
19
18
|
addSegmentBefore,
|
20
19
|
splitSegment,
|
21
20
|
deleteSegment,
|
22
|
-
updateSegment
|
21
|
+
updateSegment,
|
22
|
+
mergeSegment,
|
23
|
+
findAndReplace
|
23
24
|
} from './shared/utils/segmentOperations'
|
24
25
|
import { loadSavedData, saveData, clearSavedData } from './shared/utils/localStorage'
|
25
|
-
import { setupKeyboardHandlers, setModalHandler } from './shared/utils/keyboardHandlers'
|
26
|
+
import { setupKeyboardHandlers, setModalHandler, getModalState } from './shared/utils/keyboardHandlers'
|
26
27
|
import Header from './Header'
|
27
|
-
import {
|
28
|
+
import { getWordsFromIds } from './shared/utils/wordUtils'
|
29
|
+
import AddLyricsModal from './AddLyricsModal'
|
30
|
+
import { RestoreFromTrash, OndemandVideo } from '@mui/icons-material'
|
31
|
+
import FindReplaceModal from './FindReplaceModal'
|
32
|
+
import GlobalSyncEditor from './GlobalSyncEditor'
|
28
33
|
|
29
34
|
// Add type for window augmentation at the top of the file
|
30
35
|
declare global {
|
@@ -73,9 +78,8 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
73
78
|
const [isReviewComplete, setIsReviewComplete] = useState(false)
|
74
79
|
const [data, setData] = useState(initialData)
|
75
80
|
const [originalData] = useState(() => JSON.parse(JSON.stringify(initialData)))
|
76
|
-
const [interactionMode, setInteractionMode] = useState<InteractionMode>('
|
81
|
+
const [interactionMode, setInteractionMode] = useState<InteractionMode>('edit')
|
77
82
|
const [isShiftPressed, setIsShiftPressed] = useState(false)
|
78
|
-
const [isCtrlPressed, setIsCtrlPressed] = useState(false)
|
79
83
|
const [editModalSegment, setEditModalSegment] = useState<{
|
80
84
|
segment: LyricsSegment
|
81
85
|
index: number
|
@@ -85,6 +89,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
85
89
|
const [currentAudioTime, setCurrentAudioTime] = useState(0)
|
86
90
|
const [isUpdatingHandlers, setIsUpdatingHandlers] = useState(false)
|
87
91
|
const [flashingHandler, setFlashingHandler] = useState<string | null>(null)
|
92
|
+
const [isAddingLyrics, setIsAddingLyrics] = useState(false)
|
93
|
+
const [isAddLyricsModalOpen, setIsAddLyricsModalOpen] = useState(false)
|
94
|
+
const [isAnyModalOpen, setIsAnyModalOpen] = useState(false)
|
95
|
+
const [isFindReplaceModalOpen, setIsFindReplaceModalOpen] = useState(false)
|
96
|
+
const [isGlobalSyncEditorOpen, setIsGlobalSyncEditorOpen] = useState(false)
|
88
97
|
const theme = useTheme()
|
89
98
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
|
90
99
|
|
@@ -121,29 +130,50 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
121
130
|
|
122
131
|
// Keyboard handlers
|
123
132
|
useEffect(() => {
|
124
|
-
|
133
|
+
const { currentModalHandler } = getModalState()
|
134
|
+
|
135
|
+
console.log('LyricsAnalyzer - Setting up keyboard effect', {
|
136
|
+
isAnyModalOpen,
|
137
|
+
hasSpacebarHandler: !!currentModalHandler
|
138
|
+
})
|
125
139
|
|
126
140
|
const { handleKeyDown, handleKeyUp } = setupKeyboardHandlers({
|
127
141
|
setIsShiftPressed,
|
128
|
-
setIsCtrlPressed
|
129
142
|
})
|
130
143
|
|
131
|
-
|
144
|
+
// Always add keyboard listeners
|
145
|
+
console.log('LyricsAnalyzer - Adding keyboard event listeners')
|
132
146
|
window.addEventListener('keydown', handleKeyDown)
|
133
147
|
window.addEventListener('keyup', handleKeyUp)
|
134
148
|
|
149
|
+
// Reset modifier states when a modal opens
|
150
|
+
if (isAnyModalOpen) {
|
151
|
+
setIsShiftPressed(false)
|
152
|
+
}
|
153
|
+
|
154
|
+
// Cleanup function
|
135
155
|
return () => {
|
136
|
-
console.log('
|
156
|
+
console.log('LyricsAnalyzer - Cleanup effect running')
|
137
157
|
window.removeEventListener('keydown', handleKeyDown)
|
138
158
|
window.removeEventListener('keyup', handleKeyUp)
|
139
159
|
document.body.style.userSelect = ''
|
140
160
|
}
|
141
|
-
}, [setIsShiftPressed,
|
161
|
+
}, [setIsShiftPressed, isAnyModalOpen])
|
162
|
+
|
163
|
+
// Update modal state tracking
|
164
|
+
useEffect(() => {
|
165
|
+
const modalOpen = Boolean(
|
166
|
+
modalContent ||
|
167
|
+
editModalSegment ||
|
168
|
+
isReviewModalOpen ||
|
169
|
+
isAddLyricsModalOpen ||
|
170
|
+
isFindReplaceModalOpen
|
171
|
+
)
|
172
|
+
setIsAnyModalOpen(modalOpen)
|
173
|
+
}, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen])
|
142
174
|
|
143
175
|
// Calculate effective mode based on modifier key states
|
144
|
-
const effectiveMode = isShiftPressed ? 'highlight' :
|
145
|
-
isCtrlPressed ? 'edit' :
|
146
|
-
interactionMode
|
176
|
+
const effectiveMode = isShiftPressed ? 'highlight' : interactionMode
|
147
177
|
|
148
178
|
const handleFlash = useCallback((type: FlashType, info?: HighlightInfo) => {
|
149
179
|
setFlashingType(null)
|
@@ -217,10 +247,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
217
247
|
start_time: sourceWords[0]?.start_time ?? null,
|
218
248
|
end_time: sourceWords[sourceWords.length - 1]?.end_time ?? null
|
219
249
|
}
|
220
|
-
return [
|
221
|
-
source,
|
222
|
-
getWordsFromIds([tempSourceSegment], ids)
|
223
|
-
]
|
250
|
+
return [source, getWordsFromIds([tempSourceSegment], ids)]
|
224
251
|
})
|
225
252
|
);
|
226
253
|
|
@@ -236,13 +263,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
236
263
|
|
237
264
|
// Find if this word is part of a gap sequence
|
238
265
|
const gap = data.gap_sequences?.find(g =>
|
239
|
-
g.transcribed_word_ids.includes(info.word_id)
|
240
|
-
Object.values(g.reference_word_ids).some(ids =>
|
241
|
-
ids.includes(info.word_id)
|
242
|
-
)
|
266
|
+
g.transcribed_word_ids.includes(info.word_id)
|
243
267
|
);
|
244
268
|
|
245
269
|
if (gap) {
|
270
|
+
// Create a temporary segment containing all words
|
246
271
|
const allWords = data.corrected_segments.flatMap(s => s.words)
|
247
272
|
const tempSegment: LyricsSegment = {
|
248
273
|
id: 'temp',
|
@@ -267,10 +292,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
267
292
|
start_time: sourceWords[0]?.start_time ?? null,
|
268
293
|
end_time: sourceWords[sourceWords.length - 1]?.end_time ?? null
|
269
294
|
}
|
270
|
-
return [
|
271
|
-
source,
|
272
|
-
getWordsFromIds([tempSourceSegment], ids)
|
273
|
-
]
|
295
|
+
return [source, getWordsFromIds([tempSourceSegment], ids)]
|
274
296
|
})
|
275
297
|
);
|
276
298
|
|
@@ -297,30 +319,6 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
297
319
|
originalSegment: JSON.parse(JSON.stringify(segment))
|
298
320
|
});
|
299
321
|
}
|
300
|
-
} else if (effectiveMode === 'details') {
|
301
|
-
if (info.type === 'anchor' && info.anchor) {
|
302
|
-
const word = findWordById(data.corrected_segments, info.word_id);
|
303
|
-
setModalContent({
|
304
|
-
type: 'anchor',
|
305
|
-
data: {
|
306
|
-
...info.anchor,
|
307
|
-
wordId: info.word_id,
|
308
|
-
word: word?.text,
|
309
|
-
anchor_sequences: data.anchor_sequences
|
310
|
-
}
|
311
|
-
});
|
312
|
-
} else if (info.type === 'gap' && info.gap) {
|
313
|
-
const word = findWordById(data.corrected_segments, info.word_id);
|
314
|
-
setModalContent({
|
315
|
-
type: 'gap',
|
316
|
-
data: {
|
317
|
-
...info.gap,
|
318
|
-
wordId: info.word_id,
|
319
|
-
word: word?.text || '',
|
320
|
-
anchor_sequences: data.anchor_sequences
|
321
|
-
}
|
322
|
-
});
|
323
|
-
}
|
324
322
|
}
|
325
323
|
}, [data, effectiveMode, setModalContent]);
|
326
324
|
|
@@ -372,7 +370,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
372
370
|
setModalContent(null)
|
373
371
|
setFlashingType(null)
|
374
372
|
setHighlightInfo(null)
|
375
|
-
setInteractionMode('
|
373
|
+
setInteractionMode('edit')
|
376
374
|
}
|
377
375
|
}, [initialData])
|
378
376
|
|
@@ -389,6 +387,12 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
389
387
|
}
|
390
388
|
}, [data])
|
391
389
|
|
390
|
+
const handleMergeSegment = useCallback((segmentIndex: number, mergeWithNext: boolean) => {
|
391
|
+
const newData = mergeSegment(data, segmentIndex, mergeWithNext)
|
392
|
+
setData(newData)
|
393
|
+
setEditModalSegment(null)
|
394
|
+
}, [data])
|
395
|
+
|
392
396
|
const handleHandlerToggle = useCallback(async (handler: string, enabled: boolean) => {
|
393
397
|
if (!apiClient) return
|
394
398
|
|
@@ -443,14 +447,35 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
443
447
|
|
444
448
|
// Wrap setModalSpacebarHandler in useCallback
|
445
449
|
const handleSetModalSpacebarHandler = useCallback((handler: (() => (e: KeyboardEvent) => void) | undefined) => {
|
450
|
+
console.log('LyricsAnalyzer - Setting modal handler:', {
|
451
|
+
hasHandler: !!handler
|
452
|
+
})
|
446
453
|
// Update the global modal handler
|
447
454
|
setModalHandler(handler ? handler() : undefined, !!handler)
|
448
455
|
}, [])
|
449
456
|
|
457
|
+
// Add new handler for adding lyrics
|
458
|
+
const handleAddLyrics = useCallback(async (source: string, lyrics: string) => {
|
459
|
+
if (!apiClient) return
|
460
|
+
|
461
|
+
try {
|
462
|
+
setIsAddingLyrics(true)
|
463
|
+
const newData = await apiClient.addLyrics(source, lyrics)
|
464
|
+
setData(newData)
|
465
|
+
} finally {
|
466
|
+
setIsAddingLyrics(false)
|
467
|
+
}
|
468
|
+
}, [apiClient])
|
469
|
+
|
470
|
+
const handleFindReplace = (findText: string, replaceText: string, options: { caseSensitive: boolean, useRegex: boolean, fullTextMode: boolean }) => {
|
471
|
+
const newData = findAndReplace(data, findText, replaceText, options)
|
472
|
+
setData(newData)
|
473
|
+
}
|
474
|
+
|
450
475
|
return (
|
451
476
|
<Box sx={{
|
452
|
-
p:
|
453
|
-
pb:
|
477
|
+
p: 1,
|
478
|
+
pb: 3,
|
454
479
|
maxWidth: '100%',
|
455
480
|
overflowX: 'hidden'
|
456
481
|
}}>
|
@@ -471,9 +496,12 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
471
496
|
onHandlerToggle={handleHandlerToggle}
|
472
497
|
isUpdatingHandlers={isUpdatingHandlers}
|
473
498
|
onHandlerClick={handleHandlerClick}
|
499
|
+
onAddLyrics={() => setIsAddLyricsModalOpen(true)}
|
500
|
+
onFindReplace={() => setIsFindReplaceModalOpen(true)}
|
501
|
+
onEditSync={() => setIsGlobalSyncEditorOpen(true)}
|
474
502
|
/>
|
475
503
|
|
476
|
-
<Grid container spacing={
|
504
|
+
<Grid container spacing={1} direction={isMobile ? 'column' : 'row'}>
|
477
505
|
<Grid item xs={12} md={6}>
|
478
506
|
<TranscriptionView
|
479
507
|
data={data}
|
@@ -487,6 +515,32 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
487
515
|
currentTime={currentAudioTime}
|
488
516
|
anchors={data.anchor_sequences}
|
489
517
|
/>
|
518
|
+
{!isReadOnly && apiClient && (
|
519
|
+
<Box sx={{
|
520
|
+
mt: 2,
|
521
|
+
mb: 3,
|
522
|
+
display: 'flex',
|
523
|
+
justifyContent: 'space-between',
|
524
|
+
width: '100%'
|
525
|
+
}}>
|
526
|
+
<Button
|
527
|
+
variant="outlined"
|
528
|
+
color="warning"
|
529
|
+
onClick={handleResetCorrections}
|
530
|
+
startIcon={<RestoreFromTrash />}
|
531
|
+
>
|
532
|
+
Reset Corrections
|
533
|
+
</Button>
|
534
|
+
<Button
|
535
|
+
variant="contained"
|
536
|
+
onClick={handleFinishReview}
|
537
|
+
disabled={isReviewComplete}
|
538
|
+
endIcon={<OndemandVideo />}
|
539
|
+
>
|
540
|
+
{isReviewComplete ? 'Review Complete' : 'Preview Video'}
|
541
|
+
</Button>
|
542
|
+
</Box>
|
543
|
+
)}
|
490
544
|
</Grid>
|
491
545
|
<Grid item xs={12} md={6}>
|
492
546
|
<ReferenceView
|
@@ -506,14 +560,6 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
506
560
|
</Grid>
|
507
561
|
</Grid>
|
508
562
|
|
509
|
-
<DetailsModal
|
510
|
-
open={modalContent !== null}
|
511
|
-
content={modalContent}
|
512
|
-
onClose={() => setModalContent(null)}
|
513
|
-
allCorrections={data.corrections}
|
514
|
-
referenceLyrics={data.reference_lyrics}
|
515
|
-
/>
|
516
|
-
|
517
563
|
<EditModal
|
518
564
|
open={Boolean(editModalSegment)}
|
519
565
|
onClose={() => {
|
@@ -527,9 +573,17 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
527
573
|
onDelete={handleDeleteSegment}
|
528
574
|
onAddSegment={handleAddSegment}
|
529
575
|
onSplitSegment={handleSplitSegment}
|
576
|
+
onMergeSegment={handleMergeSegment}
|
530
577
|
onPlaySegment={handlePlaySegment}
|
531
578
|
currentTime={currentAudioTime}
|
532
579
|
setModalSpacebarHandler={handleSetModalSpacebarHandler}
|
580
|
+
originalTranscribedSegment={
|
581
|
+
editModalSegment?.segment && editModalSegment?.index !== null
|
582
|
+
? originalData.original_segments.find(
|
583
|
+
(s: LyricsSegment) => s.id === editModalSegment.segment.id
|
584
|
+
) || null
|
585
|
+
: null
|
586
|
+
}
|
533
587
|
/>
|
534
588
|
|
535
589
|
<ReviewChangesModal
|
@@ -542,23 +596,35 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
542
596
|
setModalSpacebarHandler={handleSetModalSpacebarHandler}
|
543
597
|
/>
|
544
598
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
599
|
+
<AddLyricsModal
|
600
|
+
open={isAddLyricsModalOpen}
|
601
|
+
onClose={() => setIsAddLyricsModalOpen(false)}
|
602
|
+
onSubmit={handleAddLyrics}
|
603
|
+
isSubmitting={isAddingLyrics}
|
604
|
+
/>
|
605
|
+
|
606
|
+
<FindReplaceModal
|
607
|
+
open={isFindReplaceModalOpen}
|
608
|
+
onClose={() => setIsFindReplaceModalOpen(false)}
|
609
|
+
onReplace={handleFindReplace}
|
610
|
+
data={data}
|
611
|
+
/>
|
612
|
+
|
613
|
+
{/* Add GlobalSyncEditor modal */}
|
614
|
+
{!isReadOnly && (
|
615
|
+
<GlobalSyncEditor
|
616
|
+
open={isGlobalSyncEditorOpen}
|
617
|
+
onClose={() => setIsGlobalSyncEditorOpen(false)}
|
618
|
+
segments={data.corrected_segments || []}
|
619
|
+
onSave={(updatedSegments) => {
|
620
|
+
const newData = { ...data, corrected_segments: updatedSegments };
|
621
|
+
setData(newData);
|
622
|
+
saveData(newData, initialData);
|
623
|
+
}}
|
624
|
+
onPlaySegment={handlePlaySegment}
|
625
|
+
currentTime={currentAudioTime}
|
626
|
+
setModalSpacebarHandler={handleSetModalSpacebarHandler}
|
627
|
+
/>
|
562
628
|
)}
|
563
629
|
</Box>
|
564
630
|
)
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { ToggleButton, ToggleButtonGroup, Box, Typography } from '@mui/material';
|
2
2
|
import HighlightIcon from '@mui/icons-material/Highlight';
|
3
|
-
import InfoIcon from '@mui/icons-material/Info';
|
4
3
|
import EditIcon from '@mui/icons-material/Edit';
|
5
4
|
import { InteractionMode } from '../types';
|
6
5
|
|
@@ -11,28 +10,38 @@ interface ModeSelectorProps {
|
|
11
10
|
|
12
11
|
export default function ModeSelector({ effectiveMode, onChange }: ModeSelectorProps) {
|
13
12
|
return (
|
14
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
15
|
-
<Typography variant="body2" color="text.secondary">
|
16
|
-
|
13
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.2, height: '32px' }}>
|
14
|
+
<Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.8rem' }}>
|
15
|
+
Mode:
|
17
16
|
</Typography>
|
18
17
|
<ToggleButtonGroup
|
19
18
|
value={effectiveMode}
|
20
19
|
exclusive
|
21
20
|
onChange={(_, newMode) => newMode && onChange(newMode)}
|
22
21
|
size="small"
|
22
|
+
sx={{
|
23
|
+
height: '32px',
|
24
|
+
'& .MuiToggleButton-root': {
|
25
|
+
padding: '3px 8px',
|
26
|
+
fontSize: '0.75rem',
|
27
|
+
height: '32px'
|
28
|
+
}
|
29
|
+
}}
|
23
30
|
>
|
24
|
-
<ToggleButton
|
25
|
-
|
26
|
-
|
31
|
+
<ToggleButton
|
32
|
+
value="edit"
|
33
|
+
title="Click to edit segments and make corrections in the transcription view"
|
34
|
+
>
|
35
|
+
<EditIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
|
36
|
+
Edit
|
27
37
|
</ToggleButton>
|
28
|
-
<ToggleButton
|
29
|
-
|
38
|
+
<ToggleButton
|
39
|
+
value="highlight"
|
40
|
+
title="Click words in the transcription view to highlight the matching anchor sequence in the reference lyrics. You can also hold SHIFT to temporarily activate this mode."
|
41
|
+
>
|
42
|
+
<HighlightIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
|
30
43
|
Highlight
|
31
44
|
</ToggleButton>
|
32
|
-
<ToggleButton value="edit">
|
33
|
-
<EditIcon sx={{ mr: 1 }} />
|
34
|
-
Edit
|
35
|
-
</ToggleButton>
|
36
45
|
</ToggleButtonGroup>
|
37
46
|
</Box>
|
38
47
|
);
|
@@ -12,9 +12,9 @@ import { styled } from '@mui/material/styles'
|
|
12
12
|
const SegmentControls = styled(Box)({
|
13
13
|
display: 'flex',
|
14
14
|
alignItems: 'center',
|
15
|
-
gap: '
|
16
|
-
paddingTop: '
|
17
|
-
paddingRight: '
|
15
|
+
gap: '2px',
|
16
|
+
paddingTop: '1px',
|
17
|
+
paddingRight: '4px'
|
18
18
|
})
|
19
19
|
|
20
20
|
const TextContainer = styled(Box)({
|
@@ -174,27 +174,44 @@ export default function ReferenceView({
|
|
174
174
|
};
|
175
175
|
|
176
176
|
return (
|
177
|
-
<Paper sx={{ p:
|
178
|
-
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb:
|
179
|
-
<Typography variant="h6">
|
180
|
-
Reference
|
177
|
+
<Paper sx={{ p: 0.8, position: 'relative' }}>
|
178
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
|
179
|
+
<Typography variant="h6" sx={{ fontSize: '0.9rem', mb: 0 }}>
|
180
|
+
Reference Lyrics
|
181
181
|
</Typography>
|
182
182
|
<SourceSelector
|
183
|
+
availableSources={availableSources}
|
183
184
|
currentSource={effectiveCurrentSource}
|
184
185
|
onSourceChange={onSourceChange}
|
185
|
-
availableSources={availableSources}
|
186
186
|
/>
|
187
187
|
</Box>
|
188
|
-
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
188
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.2 }}>
|
189
189
|
{currentSourceSegments.map((segment, index) => (
|
190
|
-
<Box
|
190
|
+
<Box
|
191
|
+
key={index}
|
192
|
+
sx={{
|
193
|
+
display: 'flex',
|
194
|
+
alignItems: 'flex-start',
|
195
|
+
width: '100%',
|
196
|
+
mb: 0,
|
197
|
+
'&:hover': {
|
198
|
+
backgroundColor: 'rgba(0, 0, 0, 0.03)'
|
199
|
+
}
|
200
|
+
}}
|
201
|
+
>
|
191
202
|
<SegmentControls>
|
192
203
|
<IconButton
|
193
204
|
size="small"
|
194
205
|
onClick={() => copyToClipboard(segment.words.map(w => w.text).join(' '))}
|
195
|
-
sx={{
|
206
|
+
sx={{
|
207
|
+
padding: '1px',
|
208
|
+
height: '18px',
|
209
|
+
width: '18px',
|
210
|
+
minHeight: '18px',
|
211
|
+
minWidth: '18px'
|
212
|
+
}}
|
196
213
|
>
|
197
|
-
<ContentCopyIcon
|
214
|
+
<ContentCopyIcon sx={{ fontSize: '0.9rem' }} />
|
198
215
|
</IconButton>
|
199
216
|
</SegmentControls>
|
200
217
|
<TextContainer>
|
@@ -12,6 +12,7 @@ import { CorrectionData } from '../types'
|
|
12
12
|
import { useMemo, useRef, useEffect } from 'react'
|
13
13
|
import { ApiClient } from '../api'
|
14
14
|
import PreviewVideoSection from './PreviewVideoSection'
|
15
|
+
import { CloudUpload, ArrowBack } from '@mui/icons-material'
|
15
16
|
|
16
17
|
interface ReviewChangesModalProps {
|
17
18
|
open: boolean
|
@@ -73,6 +74,13 @@ export default function ReviewChangesModal({
|
|
73
74
|
// Add ref to video element
|
74
75
|
const videoRef = useRef<HTMLVideoElement>(null)
|
75
76
|
|
77
|
+
// Stop audio playback when modal opens
|
78
|
+
useEffect(() => {
|
79
|
+
if (open && window.isAudioPlaying && window.toggleAudioPlayback) {
|
80
|
+
window.toggleAudioPlayback()
|
81
|
+
}
|
82
|
+
}, [open])
|
83
|
+
|
76
84
|
// Add effect to handle spacebar
|
77
85
|
useEffect(() => {
|
78
86
|
if (open) {
|
@@ -269,7 +277,7 @@ export default function ReviewChangesModal({
|
|
269
277
|
maxWidth="md"
|
270
278
|
fullWidth
|
271
279
|
>
|
272
|
-
<DialogTitle>
|
280
|
+
<DialogTitle>Preview Video (With Vocals)</DialogTitle>
|
273
281
|
<DialogContent
|
274
282
|
dividers
|
275
283
|
sx={{
|
@@ -288,7 +296,7 @@ export default function ReviewChangesModal({
|
|
288
296
|
{differences.length === 0 ? (
|
289
297
|
<Box>
|
290
298
|
<Typography color="text.secondary">
|
291
|
-
No
|
299
|
+
No manual corrections detected. If everything looks good in the preview, click submit and the server will generate the final karaoke video.
|
292
300
|
</Typography>
|
293
301
|
<Typography variant="body2" color="text.secondary">
|
294
302
|
Total segments: {updatedData.corrected_segments.length}
|
@@ -307,12 +315,21 @@ export default function ReviewChangesModal({
|
|
307
315
|
</Box>
|
308
316
|
</DialogContent>
|
309
317
|
<DialogActions>
|
310
|
-
<Button
|
318
|
+
<Button
|
319
|
+
onClick={onClose}
|
320
|
+
color="warning"
|
321
|
+
startIcon={<ArrowBack />}
|
322
|
+
sx={{ mr: 'auto' }}
|
323
|
+
>
|
324
|
+
Cancel
|
325
|
+
</Button>
|
311
326
|
<Button
|
312
327
|
onClick={onSubmit}
|
313
328
|
variant="contained"
|
329
|
+
color="success"
|
330
|
+
endIcon={<CloudUpload />}
|
314
331
|
>
|
315
|
-
|
332
|
+
Complete Review
|
316
333
|
</Button>
|
317
334
|
</DialogActions>
|
318
335
|
</Dialog>
|