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
@@ -41,10 +41,12 @@ 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) {
|
49
51
|
const [selectedSegmentIndex, setSelectedSegmentIndex] = useState<number | null>(null)
|
50
52
|
|
@@ -56,31 +58,49 @@ export default function TranscriptionView({
|
|
56
58
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
57
59
|
{data.corrected_segments.map((segment, segmentIndex) => {
|
58
60
|
const segmentWords: TranscriptionWordPosition[] = segment.words.map(word => {
|
61
|
+
// Find if this word is part of a correction
|
62
|
+
const correction = data.corrections?.find(c =>
|
63
|
+
c.corrected_word_id === word.id ||
|
64
|
+
c.word_id === word.id
|
65
|
+
)
|
66
|
+
|
67
|
+
// Find if this word is part of an anchor sequence
|
59
68
|
const anchor = data.anchor_sequences?.find(a =>
|
60
|
-
a
|
69
|
+
a.transcribed_word_ids.includes(word.id)
|
61
70
|
)
|
62
71
|
|
63
|
-
// If not in
|
64
|
-
const gap =
|
65
|
-
|
66
|
-
|
72
|
+
// If not in anchor, check if it belongs to a gap sequence
|
73
|
+
const gap = data.gap_sequences?.find(g => {
|
74
|
+
// Check transcribed words
|
75
|
+
const inTranscribed = g.transcribed_word_ids.includes(word.id)
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
77
|
+
// Check reference words
|
78
|
+
const inReference = Object.values(g.reference_word_ids).some(ids =>
|
79
|
+
ids.includes(word.id)
|
80
|
+
)
|
81
|
+
|
82
|
+
// Check if this word is a corrected version
|
83
|
+
const isCorrection = data.corrections.some(c =>
|
84
|
+
(c.corrected_word_id === word.id || c.word_id === word.id) &&
|
85
|
+
g.transcribed_word_ids.includes(c.word_id)
|
86
|
+
)
|
87
|
+
|
88
|
+
return inTranscribed || inReference || isCorrection
|
89
|
+
})
|
72
90
|
|
73
91
|
return {
|
74
92
|
word: {
|
75
93
|
id: word.id,
|
76
94
|
text: word.text,
|
77
|
-
start_time: word.start_time,
|
78
|
-
end_time: word.end_time
|
95
|
+
start_time: word.start_time ?? undefined,
|
96
|
+
end_time: word.end_time ?? undefined
|
79
97
|
},
|
80
98
|
type: anchor ? 'anchor' : gap ? 'gap' : 'other',
|
81
99
|
sequence: anchor || gap,
|
100
|
+
sequencePosition: anchor?.transcription_position ?? gap?.transcription_position ?? undefined,
|
82
101
|
isInRange: true,
|
83
|
-
isCorrected:
|
102
|
+
isCorrected: Boolean(correction),
|
103
|
+
gap: gap
|
84
104
|
}
|
85
105
|
})
|
86
106
|
|
@@ -93,10 +113,10 @@ export default function TranscriptionView({
|
|
93
113
|
>
|
94
114
|
{segmentIndex}
|
95
115
|
</SegmentIndex>
|
96
|
-
{segment.start_time !==
|
116
|
+
{segment.start_time !== null && (
|
97
117
|
<IconButton
|
98
118
|
size="small"
|
99
|
-
onClick={() => onPlaySegment?.(segment.start_time)}
|
119
|
+
onClick={() => onPlaySegment?.(segment.start_time!)}
|
100
120
|
sx={{ padding: '2px' }}
|
101
121
|
>
|
102
122
|
<PlayCircleOutlineIcon fontSize="small" />
|
@@ -106,14 +126,17 @@ export default function TranscriptionView({
|
|
106
126
|
<TextContainer>
|
107
127
|
<HighlightedText
|
108
128
|
wordPositions={segmentWords}
|
109
|
-
anchors={
|
129
|
+
anchors={anchors}
|
110
130
|
onElementClick={onElementClick}
|
111
131
|
onWordClick={onWordClick}
|
112
132
|
flashingType={flashingType}
|
133
|
+
flashingHandler={flashingHandler}
|
113
134
|
highlightInfo={highlightInfo}
|
114
135
|
mode={mode}
|
115
136
|
preserveSegments={true}
|
116
137
|
currentTime={currentTime}
|
138
|
+
gaps={data.gap_sequences}
|
139
|
+
corrections={data.corrections}
|
117
140
|
/>
|
118
141
|
</TextContainer>
|
119
142
|
</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,12 +49,14 @@ 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) {
|
49
61
|
const { handleWordClick } = useWordClick({
|
50
62
|
mode,
|
@@ -52,74 +64,91 @@ export function HighlightedText({
|
|
52
64
|
onWordClick,
|
53
65
|
isReference,
|
54
66
|
currentSource,
|
55
|
-
gaps
|
67
|
+
gaps,
|
68
|
+
anchors,
|
69
|
+
corrections
|
56
70
|
})
|
57
71
|
|
58
72
|
const shouldWordFlash = (wordPos: TranscriptionWordPosition | { word: string; id: string }): boolean => {
|
59
|
-
if (!flashingType)
|
73
|
+
if (!flashingType) {
|
74
|
+
return false;
|
75
|
+
}
|
60
76
|
|
61
77
|
if ('type' in wordPos) {
|
62
|
-
//
|
78
|
+
// Add handler-specific flashing
|
79
|
+
if (flashingType === 'handler' && flashingHandler) {
|
80
|
+
console.log('Checking handler flash for word:', wordPos.word.text);
|
81
|
+
console.log('Current flashingHandler:', flashingHandler);
|
82
|
+
console.log('Word ID:', wordPos.word.id);
|
83
|
+
|
84
|
+
const shouldFlash = corrections.some(correction =>
|
85
|
+
correction.handler === flashingHandler &&
|
86
|
+
(correction.corrected_word_id === wordPos.word.id ||
|
87
|
+
correction.word_id === wordPos.word.id)
|
88
|
+
);
|
89
|
+
|
90
|
+
console.log('Should flash:', shouldFlash);
|
91
|
+
return shouldFlash;
|
92
|
+
}
|
93
|
+
|
63
94
|
const gap = wordPos.sequence as GapSequence
|
64
|
-
const isCorrected =
|
65
|
-
|
66
|
-
|
67
|
-
|
95
|
+
const isCorrected = (
|
96
|
+
// Check corrections array for this word
|
97
|
+
corrections.some(correction =>
|
98
|
+
(correction.word_id === wordPos.word.id ||
|
99
|
+
correction.corrected_word_id === wordPos.word.id) &&
|
100
|
+
gap.transcribed_word_ids.includes(correction.word_id)
|
101
|
+
) ||
|
102
|
+
// Also check if marked as corrected in wordPos
|
103
|
+
wordPos.isCorrected
|
104
|
+
)
|
68
105
|
|
69
106
|
return Boolean(
|
70
107
|
(flashingType === 'anchor' && wordPos.type === 'anchor') ||
|
71
108
|
(flashingType === 'corrected' && isCorrected) ||
|
72
109
|
(flashingType === 'uncorrected' && wordPos.type === 'gap' && !isCorrected) ||
|
73
110
|
(flashingType === 'word' && (
|
74
|
-
//
|
111
|
+
// For anchors
|
75
112
|
(highlightInfo?.type === 'anchor' && wordPos.type === 'anchor' &&
|
76
|
-
|
77
|
-
|
113
|
+
(isReference && currentSource && highlightInfo.sequence
|
114
|
+
? getWordsFromIds(segments || [],
|
115
|
+
(highlightInfo.sequence as AnchorSequence).reference_word_ids[currentSource] || []
|
116
|
+
).some(w => w.id === wordPos.word.id)
|
117
|
+
: getWordsFromIds(segments || [],
|
118
|
+
(highlightInfo.sequence as AnchorSequence).transcribed_word_ids
|
119
|
+
).some(w => w.id === wordPos.word.id)
|
120
|
+
)) ||
|
121
|
+
// For gaps
|
78
122
|
(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
|
123
|
+
(isReference && currentSource && highlightInfo.sequence
|
124
|
+
? getWordsFromIds(segments || [],
|
125
|
+
(highlightInfo.sequence as GapSequence).reference_word_ids[currentSource] || []
|
126
|
+
).some(w => w.id === wordPos.word.id)
|
127
|
+
: getWordsFromIds(segments || [],
|
128
|
+
(highlightInfo.sequence as GapSequence).transcribed_word_ids
|
129
|
+
).some(w => w.id === wordPos.word.id))
|
130
|
+
) ||
|
131
|
+
// For corrections
|
132
|
+
(highlightInfo?.type === 'correction' && isReference && currentSource &&
|
133
|
+
highlightInfo.correction?.reference_positions?.[currentSource]?.toString() === wordPos.word.id)
|
115
134
|
))
|
116
135
|
)
|
117
136
|
}
|
137
|
+
return false
|
118
138
|
}
|
119
139
|
|
120
|
-
const shouldHighlightWord = (wordPos: TranscriptionWordPosition): boolean => {
|
121
|
-
|
122
|
-
|
140
|
+
const shouldHighlightWord = (wordPos: TranscriptionWordPosition | { word: string; id: string }): boolean => {
|
141
|
+
// Don't highlight words in reference view
|
142
|
+
if (isReference) return false
|
143
|
+
|
144
|
+
if ('type' in wordPos && currentTime !== undefined && 'start_time' in wordPos.word) {
|
145
|
+
const word = wordPos.word as Word
|
146
|
+
return word.start_time !== null &&
|
147
|
+
word.end_time !== null &&
|
148
|
+
currentTime >= word.start_time &&
|
149
|
+
currentTime <= word.end_time
|
150
|
+
}
|
151
|
+
return false
|
123
152
|
}
|
124
153
|
|
125
154
|
const handleCopyLine = (text: string) => {
|
@@ -127,16 +156,17 @@ export function HighlightedText({
|
|
127
156
|
};
|
128
157
|
|
129
158
|
const renderContent = () => {
|
130
|
-
if (wordPositions) {
|
159
|
+
if (wordPositions && !segments) {
|
131
160
|
return wordPositions.map((wordPos, index) => (
|
132
161
|
<React.Fragment key={wordPos.word.id}>
|
133
|
-
<
|
162
|
+
<WordComponent
|
163
|
+
key={`${wordPos.word.id}-${index}`}
|
134
164
|
word={wordPos.word.text}
|
135
165
|
shouldFlash={shouldWordFlash(wordPos)}
|
136
|
-
isCurrentlyPlaying={shouldHighlightWord(wordPos)}
|
137
166
|
isAnchor={wordPos.type === 'anchor'}
|
138
|
-
isCorrectedGap={wordPos.
|
167
|
+
isCorrectedGap={wordPos.isCorrected}
|
139
168
|
isUncorrectedGap={wordPos.type === 'gap' && !wordPos.isCorrected}
|
169
|
+
isCurrentlyPlaying={shouldHighlightWord(wordPos)}
|
140
170
|
onClick={() => handleWordClick(
|
141
171
|
wordPos.word.text,
|
142
172
|
wordPos.word.id,
|
@@ -147,6 +177,42 @@ export function HighlightedText({
|
|
147
177
|
{index < wordPositions.length - 1 && ' '}
|
148
178
|
</React.Fragment>
|
149
179
|
))
|
180
|
+
} else if (segments) {
|
181
|
+
return segments.map((segment) => (
|
182
|
+
<Box key={segment.id} sx={{ display: 'flex', alignItems: 'flex-start' }}>
|
183
|
+
<Box sx={{ flex: 1 }}>
|
184
|
+
{segment.words.map((word, wordIndex) => {
|
185
|
+
const wordPos = wordPositions.find((pos: TranscriptionWordPosition) =>
|
186
|
+
pos.word.id === word.id
|
187
|
+
);
|
188
|
+
|
189
|
+
const anchor = wordPos?.type === 'anchor' ? anchors?.find(a =>
|
190
|
+
(a.reference_word_ids[currentSource] || []).includes(word.id)
|
191
|
+
) : undefined;
|
192
|
+
|
193
|
+
const hasCorrection = referenceCorrections.has(word.id);
|
194
|
+
const isUncorrectedGap = wordPos?.type === 'gap' && !hasCorrection;
|
195
|
+
|
196
|
+
const sequence = wordPos?.type === 'gap' ? wordPos.sequence as GapSequence : undefined;
|
197
|
+
|
198
|
+
return (
|
199
|
+
<React.Fragment key={word.id}>
|
200
|
+
<WordComponent
|
201
|
+
word={word.text}
|
202
|
+
shouldFlash={shouldWordFlash(wordPos || { word: word.text, id: word.id })}
|
203
|
+
isAnchor={Boolean(anchor)}
|
204
|
+
isCorrectedGap={hasCorrection}
|
205
|
+
isUncorrectedGap={isUncorrectedGap}
|
206
|
+
isCurrentlyPlaying={shouldHighlightWord(wordPos || { word: word.text, id: word.id })}
|
207
|
+
onClick={() => handleWordClick(word.text, word.id, anchor, sequence)}
|
208
|
+
/>
|
209
|
+
{wordIndex < segment.words.length - 1 && ' '}
|
210
|
+
</React.Fragment>
|
211
|
+
);
|
212
|
+
})}
|
213
|
+
</Box>
|
214
|
+
</Box>
|
215
|
+
));
|
150
216
|
} else if (text) {
|
151
217
|
const lines = text.split('\n')
|
152
218
|
let wordCount = 0
|
@@ -219,20 +285,20 @@ export function HighlightedText({
|
|
219
285
|
wordCount++
|
220
286
|
|
221
287
|
const anchor = currentSource ? anchors?.find(a =>
|
222
|
-
a
|
288
|
+
a.reference_word_ids[currentSource]?.includes(wordId)
|
223
289
|
) : undefined
|
224
290
|
|
225
|
-
// Check if this word has a correction
|
226
291
|
const hasCorrection = referenceCorrections.has(wordId)
|
227
292
|
|
228
293
|
return (
|
229
|
-
<
|
294
|
+
<WordComponent
|
230
295
|
key={wordId}
|
231
296
|
word={word}
|
232
297
|
shouldFlash={shouldWordFlash({ word, id: wordId })}
|
233
298
|
isAnchor={Boolean(anchor)}
|
234
299
|
isCorrectedGap={hasCorrection}
|
235
300
|
isUncorrectedGap={false}
|
301
|
+
isCurrentlyPlaying={shouldHighlightWord({ word, id: wordId })}
|
236
302
|
onClick={() => handleWordClick(word, wordId, anchor, undefined)}
|
237
303
|
/>
|
238
304
|
)
|