lyrics-transcriber 0.35.0__py3-none-any.whl → 0.36.1__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 +2 -0
- lyrics_transcriber/core/config.py +1 -1
- lyrics_transcriber/core/controller.py +13 -0
- lyrics_transcriber/frontend/dist/assets/{index-CQCER5Fo.js → index-ztlAYPYT.js} +23 -23
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +18 -7
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +28 -26
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +94 -4
- lyrics_transcriber/frontend/src/components/EditModal.tsx +1 -1
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +18 -17
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +7 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +7 -4
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +17 -19
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +2 -2
- lyrics_transcriber/frontend/src/components/shared/types.ts +3 -3
- lyrics_transcriber/frontend/src/components/shared/utils/newlineCalculator.ts +1 -1
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +1 -1
- lyrics_transcriber/frontend/src/types.ts +3 -12
- lyrics_transcriber/lyrics/base_lyrics_provider.py +1 -0
- lyrics_transcriber/lyrics/file_provider.py +89 -0
- {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/RECORD +25 -24
- {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/entry_points.txt +0 -0
@@ -5,7 +5,7 @@
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7
7
|
<title>Lyrics Transcriber Analyzer</title>
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
8
|
+
<script type="module" crossorigin src="/assets/index-ztlAYPYT.js"></script>
|
9
9
|
</head>
|
10
10
|
<body>
|
11
11
|
<div id="root"></div>
|
@@ -122,20 +122,23 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
|
|
122
122
|
<Box sx={{
|
123
123
|
display: 'flex',
|
124
124
|
alignItems: 'center',
|
125
|
-
gap:
|
126
|
-
p: 2,
|
125
|
+
gap: 1,
|
127
126
|
backgroundColor: 'background.paper',
|
128
127
|
borderRadius: 1,
|
129
|
-
|
128
|
+
height: 40, // Match ToggleButtonGroup height
|
130
129
|
}}>
|
130
|
+
<Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
|
131
|
+
Playback:
|
132
|
+
</Typography>
|
133
|
+
|
131
134
|
<IconButton
|
132
135
|
onClick={handlePlayPause}
|
133
|
-
size="
|
136
|
+
size="small"
|
134
137
|
>
|
135
138
|
{isPlaying ? <PauseIcon /> : <PlayArrowIcon />}
|
136
139
|
</IconButton>
|
137
140
|
|
138
|
-
<Typography sx={{ minWidth:
|
141
|
+
<Typography variant="body2" sx={{ minWidth: 40 }}>
|
139
142
|
{formatTime(currentTime)}
|
140
143
|
</Typography>
|
141
144
|
|
@@ -144,10 +147,18 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
|
|
144
147
|
min={0}
|
145
148
|
max={duration}
|
146
149
|
onChange={handleSeek}
|
147
|
-
|
150
|
+
size="small"
|
151
|
+
sx={{
|
152
|
+
width: 200,
|
153
|
+
mx: 1,
|
154
|
+
'& .MuiSlider-thumb': {
|
155
|
+
width: 12,
|
156
|
+
height: 12,
|
157
|
+
}
|
158
|
+
}}
|
148
159
|
/>
|
149
160
|
|
150
|
-
<Typography sx={{ minWidth:
|
161
|
+
<Typography variant="body2" sx={{ minWidth: 40 }}>
|
151
162
|
{formatTime(duration)}
|
152
163
|
</Typography>
|
153
164
|
</Box>
|
@@ -6,7 +6,7 @@ interface MetricProps {
|
|
6
6
|
label: string
|
7
7
|
value: string | number
|
8
8
|
description: string
|
9
|
-
details?: Array<{ label: string, value: number }>
|
9
|
+
details?: Array<{ label: string, value: string | number }>
|
10
10
|
onClick?: () => void
|
11
11
|
}
|
12
12
|
|
@@ -38,9 +38,11 @@ function Metric({ color, label, value, description, details, onClick }: MetricPr
|
|
38
38
|
{label}
|
39
39
|
</Typography>
|
40
40
|
</Box>
|
41
|
-
<
|
42
|
-
|
43
|
-
|
41
|
+
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 1, mb: 0.5 }}>
|
42
|
+
<Typography variant="h6">
|
43
|
+
{value}
|
44
|
+
</Typography>
|
45
|
+
</Box>
|
44
46
|
<Typography variant="caption" color="text.secondary">
|
45
47
|
{description}
|
46
48
|
</Typography>
|
@@ -66,10 +68,7 @@ interface CorrectionMetricsProps {
|
|
66
68
|
// Anchor metrics
|
67
69
|
anchorCount?: number
|
68
70
|
multiSourceAnchors?: number
|
69
|
-
|
70
|
-
spotify: number
|
71
|
-
genius: number
|
72
|
-
}
|
71
|
+
anchorWordCount?: number
|
73
72
|
// Gap metrics
|
74
73
|
correctedGapCount?: number
|
75
74
|
uncorrectedGapCount?: number
|
@@ -81,6 +80,8 @@ interface CorrectionMetricsProps {
|
|
81
80
|
replacedCount?: number
|
82
81
|
addedCount?: number
|
83
82
|
deletedCount?: number
|
83
|
+
// Add total words count
|
84
|
+
totalWords?: number
|
84
85
|
onMetricClick?: {
|
85
86
|
anchor?: () => void
|
86
87
|
corrected?: () => void
|
@@ -91,21 +92,24 @@ interface CorrectionMetricsProps {
|
|
91
92
|
export default function CorrectionMetrics({
|
92
93
|
anchorCount,
|
93
94
|
multiSourceAnchors = 0,
|
94
|
-
|
95
|
+
anchorWordCount = 0,
|
95
96
|
correctedGapCount = 0,
|
96
97
|
uncorrectedGapCount = 0,
|
97
98
|
uncorrectedGaps = [],
|
98
99
|
replacedCount = 0,
|
99
100
|
addedCount = 0,
|
100
101
|
deletedCount = 0,
|
102
|
+
totalWords = 0,
|
101
103
|
onMetricClick
|
102
104
|
}: CorrectionMetricsProps) {
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
// Calculate percentages based on word counts
|
106
|
+
const anchorPercentage = totalWords > 0 ? Math.round((anchorWordCount / totalWords) * 100) : 0
|
107
|
+
const uncorrectedWordCount = uncorrectedGaps?.reduce((sum, gap) => sum + gap.length, 0) ?? 0
|
108
|
+
const uncorrectedPercentage = totalWords > 0 ? Math.round((uncorrectedWordCount / totalWords) * 100) : 0
|
109
|
+
// For corrected percentage, we'll use the total affected words
|
110
|
+
const correctedWordCount = replacedCount + addedCount
|
111
|
+
const correctedPercentage = totalWords > 0 ?
|
112
|
+
Math.round((correctedWordCount / totalWords) * 100) : 0
|
109
113
|
|
110
114
|
return (
|
111
115
|
<Grid container spacing={2}>
|
@@ -113,12 +117,11 @@ export default function CorrectionMetrics({
|
|
113
117
|
<Metric
|
114
118
|
color={COLORS.anchor}
|
115
119
|
label="Anchor Sequences"
|
116
|
-
value={anchorCount ?? '-'}
|
120
|
+
value={`${anchorCount ?? '-'} (${anchorPercentage}%)`}
|
117
121
|
description="Matched sections between transcription and reference"
|
118
122
|
details={[
|
123
|
+
{ label: "Words in Anchors", value: anchorWordCount },
|
119
124
|
{ label: "Multi-source Matches", value: multiSourceAnchors },
|
120
|
-
{ label: "Spotify Only", value: singleSourceMatches.spotify },
|
121
|
-
{ label: "Genius Only", value: singleSourceMatches.genius },
|
122
125
|
]}
|
123
126
|
onClick={onMetricClick?.anchor}
|
124
127
|
/>
|
@@ -127,12 +130,11 @@ export default function CorrectionMetrics({
|
|
127
130
|
<Metric
|
128
131
|
color={COLORS.corrected}
|
129
132
|
label="Corrected Gaps"
|
130
|
-
value={correctedGapCount
|
133
|
+
value={`${correctedGapCount} (${correctedPercentage}%)`}
|
131
134
|
description="Successfully corrected sections"
|
132
135
|
details={[
|
133
136
|
{ label: "Words Replaced", value: replacedCount },
|
134
|
-
{ label: "Words Added", value: addedCount },
|
135
|
-
{ label: "Words Deleted", value: deletedCount },
|
137
|
+
{ label: "Words Added / Deleted", value: `+${addedCount} / -${deletedCount}` },
|
136
138
|
]}
|
137
139
|
onClick={onMetricClick?.corrected}
|
138
140
|
/>
|
@@ -141,12 +143,12 @@ export default function CorrectionMetrics({
|
|
141
143
|
<Metric
|
142
144
|
color={COLORS.uncorrectedGap}
|
143
145
|
label="Uncorrected Gaps"
|
144
|
-
value={uncorrectedGapCount}
|
146
|
+
value={`${uncorrectedGapCount} (${uncorrectedPercentage}%)`}
|
145
147
|
description="Sections that may need manual review"
|
146
|
-
details={
|
147
|
-
label:
|
148
|
-
value:
|
149
|
-
}
|
148
|
+
details={[
|
149
|
+
{ label: "Words Uncorrected", value: uncorrectedWordCount },
|
150
|
+
{ label: "Number of Gaps", value: uncorrectedGapCount },
|
151
|
+
]}
|
150
152
|
onClick={onMetricClick?.uncorrected}
|
151
153
|
/>
|
152
154
|
</Grid>
|
@@ -32,6 +32,11 @@ export default function DetailsModal({
|
|
32
32
|
}
|
33
33
|
|
34
34
|
const renderContent = () => {
|
35
|
+
// Move declaration outside of case block
|
36
|
+
const correction = content.type === 'gap'
|
37
|
+
? content.data.corrections.find(c => c.original_word === content.data.word)
|
38
|
+
: null
|
39
|
+
|
35
40
|
switch (content.type) {
|
36
41
|
case 'anchor':
|
37
42
|
return (
|
@@ -46,7 +51,25 @@ export default function DetailsModal({
|
|
46
51
|
/>
|
47
52
|
<GridItem title="Position" value={content.data.position} />
|
48
53
|
<GridItem title="Length" value={`${content.data.length} words`} />
|
49
|
-
|
54
|
+
<GridItem
|
55
|
+
title="Reference Positions"
|
56
|
+
value={Object.entries(content.data.reference_positions).map(([source, pos]) => (
|
57
|
+
`${source}: ${pos}`
|
58
|
+
)).join(', ')}
|
59
|
+
/>
|
60
|
+
<GridItem title="Confidence" value={content.data.confidence.toFixed(3)} />
|
61
|
+
<GridItem
|
62
|
+
title="Phrase Score Details"
|
63
|
+
value={
|
64
|
+
<>
|
65
|
+
<Typography>Type: {content.data.phrase_score.phrase_type}</Typography>
|
66
|
+
<Typography>Natural Break: {content.data.phrase_score.natural_break_score.toFixed(3)}</Typography>
|
67
|
+
<Typography>Length: {content.data.phrase_score.length_score.toFixed(3)}</Typography>
|
68
|
+
<Typography>Total: {content.data.phrase_score.total_score.toFixed(3)}</Typography>
|
69
|
+
</>
|
70
|
+
}
|
71
|
+
/>
|
72
|
+
<GridItem title="Total Score" value={content.data.total_score.toFixed(3)} />
|
50
73
|
</Grid>
|
51
74
|
)
|
52
75
|
|
@@ -64,13 +87,80 @@ export default function DetailsModal({
|
|
64
87
|
<GridItem
|
65
88
|
title="Current Text"
|
66
89
|
value={`"${content.data.words.map(word => {
|
67
|
-
const
|
90
|
+
const wordCorrection = content.data.corrections.find(
|
68
91
|
c => c.original_word === word
|
69
92
|
)
|
70
|
-
return
|
93
|
+
return wordCorrection ? wordCorrection.corrected_word : word
|
71
94
|
}).join(' ')}"`}
|
72
95
|
/>
|
73
|
-
|
96
|
+
<GridItem
|
97
|
+
title="Position"
|
98
|
+
value={content.data.position}
|
99
|
+
/>
|
100
|
+
<GridItem
|
101
|
+
title="Length"
|
102
|
+
value={`${content.data.length} words`}
|
103
|
+
/>
|
104
|
+
{content.data.preceding_anchor && (
|
105
|
+
<GridItem
|
106
|
+
title="Preceding Anchor"
|
107
|
+
value={`"${content.data.preceding_anchor.text}"`}
|
108
|
+
/>
|
109
|
+
)}
|
110
|
+
{content.data.following_anchor && (
|
111
|
+
<GridItem
|
112
|
+
title="Following Anchor"
|
113
|
+
value={`"${content.data.following_anchor.text}"`}
|
114
|
+
/>
|
115
|
+
)}
|
116
|
+
{content.data.reference_words && (
|
117
|
+
<GridItem
|
118
|
+
title="Reference Words"
|
119
|
+
value={
|
120
|
+
<>
|
121
|
+
{Object.entries(content.data.reference_words).map(([source, words]) => (
|
122
|
+
<Typography key={source}>
|
123
|
+
{source}: "{words.join(' ')}"
|
124
|
+
</Typography>
|
125
|
+
))}
|
126
|
+
</>
|
127
|
+
}
|
128
|
+
/>
|
129
|
+
)}
|
130
|
+
{correction && (
|
131
|
+
<>
|
132
|
+
<GridItem
|
133
|
+
title="Correction Details"
|
134
|
+
value={
|
135
|
+
<>
|
136
|
+
<Typography>Original: "{correction.original_word}"</Typography>
|
137
|
+
<Typography>Corrected: "{correction.corrected_word}"</Typography>
|
138
|
+
<Typography>Confidence: {correction.confidence.toFixed(3)}</Typography>
|
139
|
+
<Typography>Source: {correction.source}</Typography>
|
140
|
+
<Typography>Reason: {correction.reason}</Typography>
|
141
|
+
{correction.is_deletion && <Typography>Is Deletion: Yes</Typography>}
|
142
|
+
{correction.split_total && (
|
143
|
+
<Typography>Split: {correction.split_index} of {correction.split_total}</Typography>
|
144
|
+
)}
|
145
|
+
</>
|
146
|
+
}
|
147
|
+
/>
|
148
|
+
{Object.entries(correction.alternatives).length > 0 && (
|
149
|
+
<GridItem
|
150
|
+
title="Alternatives"
|
151
|
+
value={
|
152
|
+
<>
|
153
|
+
{Object.entries(correction.alternatives).map(([word, score]) => (
|
154
|
+
<Typography key={word}>
|
155
|
+
"{word}": {score.toFixed(3)}
|
156
|
+
</Typography>
|
157
|
+
))}
|
158
|
+
</>
|
159
|
+
}
|
160
|
+
/>
|
161
|
+
)}
|
162
|
+
</>
|
163
|
+
)}
|
74
164
|
</Grid>
|
75
165
|
)
|
76
166
|
|
@@ -238,7 +238,7 @@ export default function EditModal({
|
|
238
238
|
}
|
239
239
|
|
240
240
|
const handleDelete = () => {
|
241
|
-
if (segmentIndex !== null
|
241
|
+
if (segmentIndex !== null) {
|
242
242
|
onDelete?.(segmentIndex)
|
243
243
|
onClose()
|
244
244
|
}
|
@@ -72,7 +72,10 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
72
72
|
const [modalContent, setModalContent] = useState<ModalContent | null>(null)
|
73
73
|
const [flashingType, setFlashingType] = useState<FlashType>(null)
|
74
74
|
const [highlightInfo, setHighlightInfo] = useState<HighlightInfo | null>(null)
|
75
|
-
const [currentSource, setCurrentSource] = useState<
|
75
|
+
const [currentSource, setCurrentSource] = useState<string>(() => {
|
76
|
+
const availableSources = Object.keys(initialData.reference_texts)
|
77
|
+
return availableSources.length > 0 ? availableSources[0] : ''
|
78
|
+
})
|
76
79
|
const [isReviewComplete, setIsReviewComplete] = useState(false)
|
77
80
|
const [data, setData] = useState(initialData)
|
78
81
|
// Create deep copy of initial data for comparison later
|
@@ -351,27 +354,13 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
351
354
|
)}
|
352
355
|
</Box>
|
353
356
|
|
354
|
-
<Box sx={{ mb: 3 }}>
|
355
|
-
<AudioPlayer
|
356
|
-
apiClient={apiClient}
|
357
|
-
onTimeUpdate={setCurrentAudioTime}
|
358
|
-
/>
|
359
|
-
</Box>
|
360
|
-
|
361
357
|
<Box sx={{ mb: 3 }}>
|
362
358
|
<CorrectionMetrics
|
363
359
|
// Anchor metrics
|
364
360
|
anchorCount={data.metadata.anchor_sequences_count}
|
365
361
|
multiSourceAnchors={data.anchor_sequences.filter(anchor =>
|
366
362
|
Object.keys(anchor.reference_positions).length > 1).length}
|
367
|
-
|
368
|
-
spotify: data.anchor_sequences.filter(anchor =>
|
369
|
-
Object.keys(anchor.reference_positions).length === 1 &&
|
370
|
-
'spotify' in anchor.reference_positions).length,
|
371
|
-
genius: data.anchor_sequences.filter(anchor =>
|
372
|
-
Object.keys(anchor.reference_positions).length === 1 &&
|
373
|
-
'genius' in anchor.reference_positions).length
|
374
|
-
}}
|
363
|
+
anchorWordCount={data.anchor_sequences.reduce((sum, anchor) => sum + anchor.length, 0)}
|
375
364
|
// Gap metrics
|
376
365
|
correctedGapCount={data.gap_sequences.filter(gap =>
|
377
366
|
gap.corrections?.length > 0).length}
|
@@ -395,14 +384,26 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
395
384
|
corrected: () => handleFlash('corrected'),
|
396
385
|
uncorrected: () => handleFlash('uncorrected')
|
397
386
|
}}
|
387
|
+
totalWords={data.metadata.total_words}
|
398
388
|
/>
|
399
389
|
</Box>
|
400
390
|
|
401
|
-
<Box sx={{
|
391
|
+
<Box sx={{
|
392
|
+
display: 'flex',
|
393
|
+
flexDirection: isMobile ? 'column' : 'row',
|
394
|
+
gap: 5,
|
395
|
+
alignItems: 'flex-start',
|
396
|
+
justifyContent: 'flex-start',
|
397
|
+
mb: 3
|
398
|
+
}}>
|
402
399
|
<ModeSelector
|
403
400
|
effectiveMode={effectiveMode}
|
404
401
|
onChange={setInteractionMode}
|
405
402
|
/>
|
403
|
+
<AudioPlayer
|
404
|
+
apiClient={apiClient}
|
405
|
+
onTimeUpdate={setCurrentAudioTime}
|
406
|
+
/>
|
406
407
|
</Box>
|
407
408
|
|
408
409
|
<Grid container spacing={2} direction={isMobile ? 'column' : 'row'}>
|
@@ -18,6 +18,12 @@ export default function ReferenceView({
|
|
18
18
|
highlightInfo,
|
19
19
|
mode
|
20
20
|
}: ReferenceViewProps) {
|
21
|
+
// Get available sources from referenceTexts object
|
22
|
+
const availableSources = useMemo(() =>
|
23
|
+
Object.keys(referenceTexts) as Array<string>,
|
24
|
+
[referenceTexts]
|
25
|
+
)
|
26
|
+
|
21
27
|
const { linePositions } = useMemo(() =>
|
22
28
|
calculateReferenceLinePositions(
|
23
29
|
corrected_segments,
|
@@ -36,6 +42,7 @@ export default function ReferenceView({
|
|
36
42
|
<SourceSelector
|
37
43
|
currentSource={currentSource}
|
38
44
|
onSourceChange={onSourceChange}
|
45
|
+
availableSources={availableSources}
|
39
46
|
/>
|
40
47
|
</Box>
|
41
48
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
@@ -8,7 +8,7 @@ import React from 'react'
|
|
8
8
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
9
9
|
import IconButton from '@mui/material/IconButton';
|
10
10
|
|
11
|
-
interface HighlightedTextProps {
|
11
|
+
export interface HighlightedTextProps {
|
12
12
|
// Input can be either raw text or pre-processed word positions
|
13
13
|
text?: string
|
14
14
|
wordPositions?: TranscriptionWordPosition[]
|
@@ -22,7 +22,7 @@ interface HighlightedTextProps {
|
|
22
22
|
flashingType: FlashType
|
23
23
|
// Reference-specific props
|
24
24
|
isReference?: boolean
|
25
|
-
currentSource?:
|
25
|
+
currentSource?: string
|
26
26
|
preserveSegments?: boolean
|
27
27
|
linePositions?: LinePosition[]
|
28
28
|
currentTime?: number
|
@@ -67,7 +67,8 @@ export function HighlightedText({
|
|
67
67
|
wordPos.type === 'anchor' && wordPos.sequence && (
|
68
68
|
(wordPos.sequence as AnchorSequence).transcription_position === highlightInfo.transcriptionIndex ||
|
69
69
|
(isReference && currentSource &&
|
70
|
-
(wordPos.sequence as AnchorSequence).reference_positions[currentSource
|
70
|
+
(wordPos.sequence as AnchorSequence).reference_positions[currentSource as keyof typeof highlightInfo.referenceIndices] ===
|
71
|
+
highlightInfo.referenceIndices?.[currentSource as keyof typeof highlightInfo.referenceIndices])
|
71
72
|
))
|
72
73
|
)
|
73
74
|
} else {
|
@@ -85,7 +86,9 @@ export function HighlightedText({
|
|
85
86
|
(flashingType === 'anchor' && anchor) ||
|
86
87
|
(flashingType === 'word' && highlightInfo?.type === 'anchor' && anchor && (
|
87
88
|
anchor.transcription_position === highlightInfo.transcriptionIndex ||
|
88
|
-
(isReference && currentSource &&
|
89
|
+
(isReference && currentSource &&
|
90
|
+
anchor.reference_positions[currentSource as keyof typeof highlightInfo.referenceIndices] ===
|
91
|
+
highlightInfo.referenceIndices?.[currentSource as keyof typeof highlightInfo.referenceIndices])
|
89
92
|
))
|
90
93
|
)
|
91
94
|
}
|
@@ -1,28 +1,26 @@
|
|
1
1
|
import { Box, Button } from '@mui/material'
|
2
2
|
|
3
|
-
interface SourceSelectorProps {
|
4
|
-
currentSource:
|
5
|
-
onSourceChange: (source:
|
3
|
+
export interface SourceSelectorProps {
|
4
|
+
currentSource: string
|
5
|
+
onSourceChange: (source: string) => void
|
6
|
+
availableSources: string[]
|
6
7
|
}
|
7
8
|
|
8
|
-
export function SourceSelector({ currentSource, onSourceChange }: SourceSelectorProps) {
|
9
|
+
export function SourceSelector({ currentSource, onSourceChange, availableSources }: SourceSelectorProps) {
|
9
10
|
return (
|
10
11
|
<Box>
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
>
|
24
|
-
Spotify
|
25
|
-
</Button>
|
12
|
+
{availableSources.map((source) => (
|
13
|
+
<Button
|
14
|
+
key={source}
|
15
|
+
size="small"
|
16
|
+
variant={currentSource === source ? 'contained' : 'outlined'}
|
17
|
+
onClick={() => onSourceChange(source)}
|
18
|
+
sx={{ mr: 1 }}
|
19
|
+
>
|
20
|
+
{/* Capitalize first letter of source */}
|
21
|
+
{source.charAt(0).toUpperCase() + source.slice(1)}
|
22
|
+
</Button>
|
23
|
+
))}
|
26
24
|
</Box>
|
27
25
|
)
|
28
26
|
}
|
@@ -3,12 +3,12 @@ import { AnchorSequence, GapSequence, InteractionMode } from '../../../types'
|
|
3
3
|
import { ModalContent } from '../../LyricsAnalyzer'
|
4
4
|
import { WordClickInfo } from '../types'
|
5
5
|
|
6
|
-
interface UseWordClickProps {
|
6
|
+
export interface UseWordClickProps {
|
7
7
|
mode: InteractionMode
|
8
8
|
onElementClick: (content: ModalContent) => void
|
9
9
|
onWordClick?: (info: WordClickInfo) => void
|
10
10
|
isReference?: boolean
|
11
|
-
currentSource?:
|
11
|
+
currentSource?: string
|
12
12
|
}
|
13
13
|
|
14
14
|
export function useWordClick({
|
@@ -81,8 +81,8 @@ export interface ReferenceViewProps extends BaseViewProps {
|
|
81
81
|
referenceTexts: Record<string, string>
|
82
82
|
anchors: LyricsData['anchor_sequences']
|
83
83
|
gaps: LyricsData['gap_sequences']
|
84
|
-
currentSource:
|
85
|
-
onSourceChange: (source:
|
84
|
+
currentSource: string
|
85
|
+
onSourceChange: (source: string) => void
|
86
86
|
corrected_segments: LyricsSegment[]
|
87
87
|
}
|
88
88
|
|
@@ -93,7 +93,7 @@ export interface HighlightedTextProps extends BaseViewProps {
|
|
93
93
|
anchors: AnchorSequence[]
|
94
94
|
gaps: GapSequence[]
|
95
95
|
isReference?: boolean
|
96
|
-
currentSource?:
|
96
|
+
currentSource?: string
|
97
97
|
preserveSegments?: boolean
|
98
98
|
linePositions?: LinePosition[]
|
99
99
|
}
|
@@ -3,7 +3,7 @@ import { LyricsData, LyricsSegment } from '../../../types'
|
|
3
3
|
export function calculateNewlineIndices(
|
4
4
|
corrected_segments: LyricsSegment[],
|
5
5
|
anchors: LyricsData['anchor_sequences'],
|
6
|
-
currentSource:
|
6
|
+
currentSource: string
|
7
7
|
): Set<number> {
|
8
8
|
return new Set(
|
9
9
|
corrected_segments.slice(0, -1).map((segment, segmentIndex) => {
|
@@ -4,7 +4,7 @@ import { LinePosition } from '../types'
|
|
4
4
|
export function calculateReferenceLinePositions(
|
5
5
|
corrected_segments: LyricsSegment[],
|
6
6
|
anchors: LyricsData['anchor_sequences'],
|
7
|
-
currentSource:
|
7
|
+
currentSource: string
|
8
8
|
): { linePositions: LinePosition[] } {
|
9
9
|
const linePositions: LinePosition[] = []
|
10
10
|
let currentReferencePosition = 0
|
@@ -53,14 +53,8 @@ export interface GapSequence {
|
|
53
53
|
transcription_position: number
|
54
54
|
preceding_anchor: AnchorSequence | null
|
55
55
|
following_anchor: AnchorSequence | null
|
56
|
-
reference_words:
|
57
|
-
|
58
|
-
genius?: string[]
|
59
|
-
}
|
60
|
-
reference_words_original?: {
|
61
|
-
spotify?: string[]
|
62
|
-
genius?: string[]
|
63
|
-
}
|
56
|
+
reference_words: Record<string, string[]>
|
57
|
+
reference_words_original?: Record<string, string[]>
|
64
58
|
corrections: WordCorrection[]
|
65
59
|
}
|
66
60
|
|
@@ -106,10 +100,7 @@ export interface CorrectionData {
|
|
106
100
|
export interface HighlightInfo {
|
107
101
|
transcriptionIndex?: number
|
108
102
|
transcriptionLength?: number
|
109
|
-
referenceIndices:
|
110
|
-
genius?: number
|
111
|
-
spotify?: number
|
112
|
-
}
|
103
|
+
referenceIndices: Record<string, number>
|
113
104
|
referenceLength?: number
|
114
105
|
type: 'single' | 'gap' | 'anchor'
|
115
106
|
}
|
@@ -16,6 +16,7 @@ class LyricsProviderConfig:
|
|
16
16
|
|
17
17
|
genius_api_token: Optional[str] = None
|
18
18
|
spotify_cookie: Optional[str] = None
|
19
|
+
lyrics_file: Optional[str] = None
|
19
20
|
cache_dir: Optional[str] = None
|
20
21
|
audio_filepath: Optional[str] = None
|
21
22
|
max_line_length: int = 36 # New config parameter for KaraokeLyricsProcessor
|