lyrics-transcriber 0.40.0__py3-none-any.whl → 0.42.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/cli/cli_main.py +7 -0
- lyrics_transcriber/core/config.py +1 -0
- 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-coH8y7gV.js} +16284 -9032
- lyrics_transcriber/frontend/dist/assets/index-coH8y7gV.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 +7 -6
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +86 -59
- lyrics_transcriber/frontend/src/components/EditModal.tsx +93 -43
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +2 -2
- lyrics_transcriber/frontend/src/components/Header.tsx +251 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +303 -265
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +117 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +125 -40
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +129 -115
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +59 -78
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +40 -16
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +4 -10
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +137 -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 +35 -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.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 +8 -8
- lyrics_transcriber/output/generator.py +29 -14
- lyrics_transcriber/output/plain_text.py +15 -10
- lyrics_transcriber/output/segment_resizer.py +16 -3
- lyrics_transcriber/output/subtitles.py +56 -2
- 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.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/METADATA +3 -1
- {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/RECORD +76 -63
- {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.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.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/entry_points.txt +0 -0
@@ -41,11 +41,14 @@ export default function TranscriptionView({
|
|
41
41
|
onElementClick,
|
42
42
|
onWordClick,
|
43
43
|
flashingType,
|
44
|
+
flashingHandler,
|
44
45
|
highlightInfo,
|
45
46
|
mode,
|
46
47
|
onPlaySegment,
|
47
|
-
currentTime = 0
|
48
|
+
currentTime = 0,
|
49
|
+
anchors = []
|
48
50
|
}: TranscriptionViewProps) {
|
51
|
+
console.log('TranscriptionView props:', { flashingType, flashingHandler });
|
49
52
|
const [selectedSegmentIndex, setSelectedSegmentIndex] = useState<number | null>(null)
|
50
53
|
|
51
54
|
return (
|
@@ -56,31 +59,49 @@ export default function TranscriptionView({
|
|
56
59
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
57
60
|
{data.corrected_segments.map((segment, segmentIndex) => {
|
58
61
|
const segmentWords: TranscriptionWordPosition[] = segment.words.map(word => {
|
62
|
+
// Find if this word is part of a correction
|
63
|
+
const correction = data.corrections?.find(c =>
|
64
|
+
c.corrected_word_id === word.id ||
|
65
|
+
c.word_id === word.id
|
66
|
+
)
|
67
|
+
|
68
|
+
// Find if this word is part of an anchor sequence
|
59
69
|
const anchor = data.anchor_sequences?.find(a =>
|
60
|
-
a
|
70
|
+
a.transcribed_word_ids.includes(word.id)
|
61
71
|
)
|
62
72
|
|
63
|
-
// If not in
|
64
|
-
const gap =
|
65
|
-
|
66
|
-
|
73
|
+
// If not in anchor, check if it belongs to a gap sequence
|
74
|
+
const gap = data.gap_sequences?.find(g => {
|
75
|
+
// Check transcribed words
|
76
|
+
const inTranscribed = g.transcribed_word_ids.includes(word.id)
|
67
77
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
78
|
+
// Check reference words
|
79
|
+
const inReference = Object.values(g.reference_word_ids).some(ids =>
|
80
|
+
ids.includes(word.id)
|
81
|
+
)
|
82
|
+
|
83
|
+
// Check if this word is a corrected version
|
84
|
+
const isCorrection = data.corrections.some(c =>
|
85
|
+
(c.corrected_word_id === word.id || c.word_id === word.id) &&
|
86
|
+
g.transcribed_word_ids.includes(c.word_id)
|
87
|
+
)
|
88
|
+
|
89
|
+
return inTranscribed || inReference || isCorrection
|
90
|
+
})
|
72
91
|
|
73
92
|
return {
|
74
93
|
word: {
|
75
94
|
id: word.id,
|
76
95
|
text: word.text,
|
77
|
-
start_time: word.start_time,
|
78
|
-
end_time: word.end_time
|
96
|
+
start_time: word.start_time ?? undefined,
|
97
|
+
end_time: word.end_time ?? undefined
|
79
98
|
},
|
80
99
|
type: anchor ? 'anchor' : gap ? 'gap' : 'other',
|
81
100
|
sequence: anchor || gap,
|
101
|
+
sequencePosition: anchor?.transcription_position ?? gap?.transcription_position ?? undefined,
|
82
102
|
isInRange: true,
|
83
|
-
isCorrected:
|
103
|
+
isCorrected: Boolean(correction),
|
104
|
+
gap: gap
|
84
105
|
}
|
85
106
|
})
|
86
107
|
|
@@ -93,10 +114,10 @@ export default function TranscriptionView({
|
|
93
114
|
>
|
94
115
|
{segmentIndex}
|
95
116
|
</SegmentIndex>
|
96
|
-
{segment.start_time !==
|
117
|
+
{segment.start_time !== null && (
|
97
118
|
<IconButton
|
98
119
|
size="small"
|
99
|
-
onClick={() => onPlaySegment?.(segment.start_time)}
|
120
|
+
onClick={() => onPlaySegment?.(segment.start_time!)}
|
100
121
|
sx={{ padding: '2px' }}
|
101
122
|
>
|
102
123
|
<PlayCircleOutlineIcon fontSize="small" />
|
@@ -106,14 +127,17 @@ export default function TranscriptionView({
|
|
106
127
|
<TextContainer>
|
107
128
|
<HighlightedText
|
108
129
|
wordPositions={segmentWords}
|
109
|
-
anchors={
|
130
|
+
anchors={anchors}
|
110
131
|
onElementClick={onElementClick}
|
111
132
|
onWordClick={onWordClick}
|
112
133
|
flashingType={flashingType}
|
134
|
+
flashingHandler={flashingHandler}
|
113
135
|
highlightInfo={highlightInfo}
|
114
136
|
mode={mode}
|
115
137
|
preserveSegments={true}
|
116
138
|
currentTime={currentTime}
|
139
|
+
gaps={data.gap_sequences}
|
140
|
+
corrections={data.corrections}
|
117
141
|
/>
|
118
142
|
</TextContainer>
|
119
143
|
</Box>
|
@@ -15,7 +15,7 @@ export function useWordEdit(content: ModalContent | null) {
|
|
15
15
|
|
16
16
|
useEffect(() => {
|
17
17
|
if (content) {
|
18
|
-
setEditedWord(content.
|
18
|
+
setEditedWord(content.data.word || '')
|
19
19
|
setIsEditing(false)
|
20
20
|
}
|
21
21
|
}, [content])
|
@@ -37,11 +37,7 @@ export default function WordEditControls({ content, onUpdateCorrection, onClose
|
|
37
37
|
} = useWordEdit(content)
|
38
38
|
|
39
39
|
const handleStartEdit = () => {
|
40
|
-
|
41
|
-
setEditedWord(content.data.word)
|
42
|
-
} else if (content.type === 'anchor') {
|
43
|
-
setEditedWord(content.data.words[0])
|
44
|
-
}
|
40
|
+
setEditedWord(content.data.word || '')
|
45
41
|
setIsEditing(true)
|
46
42
|
}
|
47
43
|
|
@@ -59,10 +55,8 @@ export default function WordEditControls({ content, onUpdateCorrection, onClose
|
|
59
55
|
}
|
60
56
|
|
61
57
|
const handleCancelEdit = () => {
|
62
|
-
|
63
|
-
|
64
|
-
setIsEditing(false)
|
65
|
-
}
|
58
|
+
setEditedWord(content.data.word || '')
|
59
|
+
setIsEditing(false)
|
66
60
|
}
|
67
61
|
|
68
62
|
const handleWordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
@@ -1,25 +1,32 @@
|
|
1
1
|
import { Typography, Box } from '@mui/material'
|
2
|
-
import {
|
2
|
+
import { WordComponent } from './Word'
|
3
3
|
import { useWordClick } from '../hooks/useWordClick'
|
4
|
-
import {
|
4
|
+
import {
|
5
|
+
AnchorSequence,
|
6
|
+
GapSequence,
|
7
|
+
HighlightInfo,
|
8
|
+
InteractionMode,
|
9
|
+
LyricsSegment,
|
10
|
+
Word,
|
11
|
+
WordCorrection
|
12
|
+
} from '../../../types'
|
5
13
|
import { ModalContent } from '../../LyricsAnalyzer'
|
6
|
-
import {
|
14
|
+
import type { FlashType, LinePosition, TranscriptionWordPosition, WordClickInfo } from '../types'
|
7
15
|
import React from 'react'
|
8
|
-
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
9
|
-
import IconButton from '@mui/material/IconButton'
|
16
|
+
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
17
|
+
import IconButton from '@mui/material/IconButton'
|
18
|
+
import { getWordsFromIds } from '../utils/wordUtils'
|
10
19
|
|
11
20
|
export interface HighlightedTextProps {
|
12
|
-
// Input can be either raw text or pre-processed word positions
|
13
21
|
text?: string
|
14
|
-
|
15
|
-
|
22
|
+
segments?: LyricsSegment[]
|
23
|
+
wordPositions: TranscriptionWordPosition[]
|
16
24
|
anchors: AnchorSequence[]
|
17
25
|
highlightInfo: HighlightInfo | null
|
18
26
|
mode: InteractionMode
|
19
27
|
onElementClick: (content: ModalContent) => void
|
20
28
|
onWordClick?: (info: WordClickInfo) => void
|
21
29
|
flashingType: FlashType
|
22
|
-
// Reference-specific props
|
23
30
|
isReference?: boolean
|
24
31
|
currentSource?: string
|
25
32
|
preserveSegments?: boolean
|
@@ -27,11 +34,14 @@ export interface HighlightedTextProps {
|
|
27
34
|
currentTime?: number
|
28
35
|
referenceCorrections?: Map<string, string>
|
29
36
|
gaps?: GapSequence[]
|
37
|
+
flashingHandler?: string | null
|
38
|
+
corrections?: WordCorrection[]
|
30
39
|
}
|
31
40
|
|
32
41
|
export function HighlightedText({
|
33
42
|
text,
|
34
|
-
|
43
|
+
segments,
|
44
|
+
wordPositions = [] as TranscriptionWordPosition[],
|
35
45
|
anchors,
|
36
46
|
highlightInfo,
|
37
47
|
mode,
|
@@ -39,87 +49,109 @@ export function HighlightedText({
|
|
39
49
|
onWordClick,
|
40
50
|
flashingType,
|
41
51
|
isReference,
|
42
|
-
currentSource,
|
52
|
+
currentSource = '',
|
43
53
|
preserveSegments = false,
|
44
54
|
linePositions = [],
|
45
55
|
currentTime = 0,
|
46
56
|
referenceCorrections = new Map(),
|
47
|
-
gaps = []
|
57
|
+
gaps = [],
|
58
|
+
flashingHandler,
|
59
|
+
corrections = [],
|
48
60
|
}: HighlightedTextProps) {
|
61
|
+
console.log('HighlightedText props:', { flashingType, flashingHandler });
|
62
|
+
|
49
63
|
const { handleWordClick } = useWordClick({
|
50
64
|
mode,
|
51
65
|
onElementClick,
|
52
66
|
onWordClick,
|
53
67
|
isReference,
|
54
68
|
currentSource,
|
55
|
-
gaps
|
69
|
+
gaps,
|
70
|
+
anchors,
|
71
|
+
corrections
|
56
72
|
})
|
57
73
|
|
58
74
|
const shouldWordFlash = (wordPos: TranscriptionWordPosition | { word: string; id: string }): boolean => {
|
59
|
-
if (!flashingType)
|
75
|
+
if (!flashingType) {
|
76
|
+
console.log('No flashingType');
|
77
|
+
return false;
|
78
|
+
}
|
60
79
|
|
61
80
|
if ('type' in wordPos) {
|
62
|
-
//
|
81
|
+
// Add handler-specific flashing
|
82
|
+
if (flashingType === 'handler' && flashingHandler) {
|
83
|
+
console.log('Checking handler flash for word:', wordPos.word.text);
|
84
|
+
console.log('Current flashingHandler:', flashingHandler);
|
85
|
+
console.log('Word ID:', wordPos.word.id);
|
86
|
+
|
87
|
+
const shouldFlash = corrections.some(correction =>
|
88
|
+
correction.handler === flashingHandler &&
|
89
|
+
(correction.corrected_word_id === wordPos.word.id ||
|
90
|
+
correction.word_id === wordPos.word.id)
|
91
|
+
);
|
92
|
+
|
93
|
+
console.log('Should flash:', shouldFlash);
|
94
|
+
return shouldFlash;
|
95
|
+
}
|
96
|
+
|
63
97
|
const gap = wordPos.sequence as GapSequence
|
64
|
-
const isCorrected =
|
65
|
-
|
66
|
-
|
67
|
-
|
98
|
+
const isCorrected = (
|
99
|
+
// Check corrections array for this word
|
100
|
+
corrections.some(correction =>
|
101
|
+
(correction.word_id === wordPos.word.id ||
|
102
|
+
correction.corrected_word_id === wordPos.word.id) &&
|
103
|
+
gap.transcribed_word_ids.includes(correction.word_id)
|
104
|
+
) ||
|
105
|
+
// Also check if marked as corrected in wordPos
|
106
|
+
wordPos.isCorrected
|
107
|
+
)
|
68
108
|
|
69
109
|
return Boolean(
|
70
110
|
(flashingType === 'anchor' && wordPos.type === 'anchor') ||
|
71
111
|
(flashingType === 'corrected' && isCorrected) ||
|
72
112
|
(flashingType === 'uncorrected' && wordPos.type === 'gap' && !isCorrected) ||
|
73
113
|
(flashingType === 'word' && (
|
74
|
-
//
|
114
|
+
// For anchors
|
75
115
|
(highlightInfo?.type === 'anchor' && wordPos.type === 'anchor' &&
|
76
|
-
|
77
|
-
|
116
|
+
(isReference && currentSource && highlightInfo.sequence
|
117
|
+
? getWordsFromIds(segments || [],
|
118
|
+
(highlightInfo.sequence as AnchorSequence).reference_word_ids[currentSource] || []
|
119
|
+
).some(w => w.id === wordPos.word.id)
|
120
|
+
: getWordsFromIds(segments || [],
|
121
|
+
(highlightInfo.sequence as AnchorSequence).transcribed_word_ids
|
122
|
+
).some(w => w.id === wordPos.word.id)
|
123
|
+
)) ||
|
124
|
+
// For gaps
|
78
125
|
(highlightInfo?.type === 'gap' && wordPos.type === 'gap' &&
|
79
|
-
highlightInfo.
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
// Check if this word should flash as part of a gap
|
91
|
-
const shouldFlashGap = flashingType === 'word' &&
|
92
|
-
highlightInfo?.type === 'gap' &&
|
93
|
-
// Check if this reference word corresponds to the clicked word
|
94
|
-
gaps?.some(gap =>
|
95
|
-
gap.corrections.some(correction => {
|
96
|
-
// Only flash if this correction corresponds to the clicked word
|
97
|
-
if (!highlightInfo.word_ids?.includes(correction.word_id)) {
|
98
|
-
return false;
|
99
|
-
}
|
100
|
-
|
101
|
-
const refPosition = correction.reference_positions?.[currentSource];
|
102
|
-
const wordPosition = parseInt(wordPos.id.split('-').pop() || '', 10);
|
103
|
-
return typeof refPosition === 'number' && refPosition === wordPosition;
|
104
|
-
})
|
105
|
-
)
|
106
|
-
|
107
|
-
return Boolean(
|
108
|
-
(flashingType === 'anchor' && anchor) ||
|
109
|
-
(flashingType === 'word' && (
|
110
|
-
// Handle anchor highlighting
|
111
|
-
(highlightInfo?.type === 'anchor' &&
|
112
|
-
highlightInfo.reference_word_ids?.[currentSource]?.includes(wordPos.id)) ||
|
113
|
-
// Handle gap highlighting
|
114
|
-
shouldFlashGap
|
126
|
+
(isReference && currentSource && highlightInfo.sequence
|
127
|
+
? getWordsFromIds(segments || [],
|
128
|
+
(highlightInfo.sequence as GapSequence).reference_word_ids[currentSource] || []
|
129
|
+
).some(w => w.id === wordPos.word.id)
|
130
|
+
: getWordsFromIds(segments || [],
|
131
|
+
(highlightInfo.sequence as GapSequence).transcribed_word_ids
|
132
|
+
).some(w => w.id === wordPos.word.id))
|
133
|
+
) ||
|
134
|
+
// For corrections
|
135
|
+
(highlightInfo?.type === 'correction' && isReference && currentSource &&
|
136
|
+
highlightInfo.correction?.reference_positions?.[currentSource]?.toString() === wordPos.word.id)
|
115
137
|
))
|
116
138
|
)
|
117
139
|
}
|
140
|
+
return false
|
118
141
|
}
|
119
142
|
|
120
|
-
const shouldHighlightWord = (wordPos: TranscriptionWordPosition): boolean => {
|
121
|
-
|
122
|
-
|
143
|
+
const shouldHighlightWord = (wordPos: TranscriptionWordPosition | { word: string; id: string }): boolean => {
|
144
|
+
// Don't highlight words in reference view
|
145
|
+
if (isReference) return false
|
146
|
+
|
147
|
+
if ('type' in wordPos && currentTime !== undefined && 'start_time' in wordPos.word) {
|
148
|
+
const word = wordPos.word as Word
|
149
|
+
return word.start_time !== null &&
|
150
|
+
word.end_time !== null &&
|
151
|
+
currentTime >= word.start_time &&
|
152
|
+
currentTime <= word.end_time
|
153
|
+
}
|
154
|
+
return false
|
123
155
|
}
|
124
156
|
|
125
157
|
const handleCopyLine = (text: string) => {
|
@@ -127,16 +159,17 @@ export function HighlightedText({
|
|
127
159
|
};
|
128
160
|
|
129
161
|
const renderContent = () => {
|
130
|
-
if (wordPositions) {
|
162
|
+
if (wordPositions && !segments) {
|
131
163
|
return wordPositions.map((wordPos, index) => (
|
132
164
|
<React.Fragment key={wordPos.word.id}>
|
133
|
-
<
|
165
|
+
<WordComponent
|
166
|
+
key={`${wordPos.word.id}-${index}`}
|
134
167
|
word={wordPos.word.text}
|
135
168
|
shouldFlash={shouldWordFlash(wordPos)}
|
136
|
-
isCurrentlyPlaying={shouldHighlightWord(wordPos)}
|
137
169
|
isAnchor={wordPos.type === 'anchor'}
|
138
|
-
isCorrectedGap={wordPos.
|
170
|
+
isCorrectedGap={wordPos.isCorrected}
|
139
171
|
isUncorrectedGap={wordPos.type === 'gap' && !wordPos.isCorrected}
|
172
|
+
isCurrentlyPlaying={shouldHighlightWord(wordPos)}
|
140
173
|
onClick={() => handleWordClick(
|
141
174
|
wordPos.word.text,
|
142
175
|
wordPos.word.id,
|
@@ -147,6 +180,42 @@ export function HighlightedText({
|
|
147
180
|
{index < wordPositions.length - 1 && ' '}
|
148
181
|
</React.Fragment>
|
149
182
|
))
|
183
|
+
} else if (segments) {
|
184
|
+
return segments.map((segment) => (
|
185
|
+
<Box key={segment.id} sx={{ display: 'flex', alignItems: 'flex-start' }}>
|
186
|
+
<Box sx={{ flex: 1 }}>
|
187
|
+
{segment.words.map((word, wordIndex) => {
|
188
|
+
const wordPos = wordPositions.find((pos: TranscriptionWordPosition) =>
|
189
|
+
pos.word.id === word.id
|
190
|
+
);
|
191
|
+
|
192
|
+
const anchor = wordPos?.type === 'anchor' ? anchors?.find(a =>
|
193
|
+
(a.reference_word_ids[currentSource] || []).includes(word.id)
|
194
|
+
) : undefined;
|
195
|
+
|
196
|
+
const hasCorrection = referenceCorrections.has(word.id);
|
197
|
+
const isUncorrectedGap = wordPos?.type === 'gap' && !hasCorrection;
|
198
|
+
|
199
|
+
const sequence = wordPos?.type === 'gap' ? wordPos.sequence as GapSequence : undefined;
|
200
|
+
|
201
|
+
return (
|
202
|
+
<React.Fragment key={word.id}>
|
203
|
+
<WordComponent
|
204
|
+
word={word.text}
|
205
|
+
shouldFlash={shouldWordFlash(wordPos || { word: word.text, id: word.id })}
|
206
|
+
isAnchor={Boolean(anchor)}
|
207
|
+
isCorrectedGap={hasCorrection}
|
208
|
+
isUncorrectedGap={isUncorrectedGap}
|
209
|
+
isCurrentlyPlaying={shouldHighlightWord(wordPos || { word: word.text, id: word.id })}
|
210
|
+
onClick={() => handleWordClick(word.text, word.id, anchor, sequence)}
|
211
|
+
/>
|
212
|
+
{wordIndex < segment.words.length - 1 && ' '}
|
213
|
+
</React.Fragment>
|
214
|
+
);
|
215
|
+
})}
|
216
|
+
</Box>
|
217
|
+
</Box>
|
218
|
+
));
|
150
219
|
} else if (text) {
|
151
220
|
const lines = text.split('\n')
|
152
221
|
let wordCount = 0
|
@@ -219,20 +288,20 @@ export function HighlightedText({
|
|
219
288
|
wordCount++
|
220
289
|
|
221
290
|
const anchor = currentSource ? anchors?.find(a =>
|
222
|
-
a
|
291
|
+
a.reference_word_ids[currentSource]?.includes(wordId)
|
223
292
|
) : undefined
|
224
293
|
|
225
|
-
// Check if this word has a correction
|
226
294
|
const hasCorrection = referenceCorrections.has(wordId)
|
227
295
|
|
228
296
|
return (
|
229
|
-
<
|
297
|
+
<WordComponent
|
230
298
|
key={wordId}
|
231
299
|
word={word}
|
232
300
|
shouldFlash={shouldWordFlash({ word, id: wordId })}
|
233
301
|
isAnchor={Boolean(anchor)}
|
234
302
|
isCorrectedGap={hasCorrection}
|
235
303
|
isUncorrectedGap={false}
|
304
|
+
isCurrentlyPlaying={shouldHighlightWord({ word, id: wordId })}
|
236
305
|
onClick={() => handleWordClick(word, wordId, anchor, undefined)}
|
237
306
|
/>
|
238
307
|
)
|