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.
Files changed (25) hide show
  1. lyrics_transcriber/cli/cli_main.py +2 -0
  2. lyrics_transcriber/core/config.py +1 -1
  3. lyrics_transcriber/core/controller.py +13 -0
  4. lyrics_transcriber/frontend/dist/assets/{index-CQCER5Fo.js → index-ztlAYPYT.js} +23 -23
  5. lyrics_transcriber/frontend/dist/index.html +1 -1
  6. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +18 -7
  7. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +28 -26
  8. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +94 -4
  9. lyrics_transcriber/frontend/src/components/EditModal.tsx +1 -1
  10. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +18 -17
  11. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +7 -0
  12. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +7 -4
  13. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +17 -19
  14. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +2 -2
  15. lyrics_transcriber/frontend/src/components/shared/types.ts +3 -3
  16. lyrics_transcriber/frontend/src/components/shared/utils/newlineCalculator.ts +1 -1
  17. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +1 -1
  18. lyrics_transcriber/frontend/src/types.ts +3 -12
  19. lyrics_transcriber/lyrics/base_lyrics_provider.py +1 -0
  20. lyrics_transcriber/lyrics/file_provider.py +89 -0
  21. {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/METADATA +1 -1
  22. {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/RECORD +25 -24
  23. {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/LICENSE +0 -0
  24. {lyrics_transcriber-0.35.0.dist-info → lyrics_transcriber-0.36.1.dist-info}/WHEEL +0 -0
  25. {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-CQCER5Fo.js"></script>
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: 2,
126
- p: 2,
125
+ gap: 1,
127
126
  backgroundColor: 'background.paper',
128
127
  borderRadius: 1,
129
- boxShadow: 1
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="large"
136
+ size="small"
134
137
  >
135
138
  {isPlaying ? <PauseIcon /> : <PlayArrowIcon />}
136
139
  </IconButton>
137
140
 
138
- <Typography sx={{ minWidth: 45 }}>
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
- sx={{ mx: 2 }}
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: 45 }}>
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
- <Typography variant="h6">
42
- {value}
43
- </Typography>
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
- singleSourceMatches?: {
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
- singleSourceMatches = { spotify: 0, genius: 0 },
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
- const formatPositionLabel = (position: number, length: number) => {
104
- if (length === 1) {
105
- return `Position ${position}`;
106
- }
107
- return `Positions ${position}-${position + length - 1}`;
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={uncorrectedGaps.map(gap => ({
147
- label: formatPositionLabel(gap.position, gap.length),
148
- value: gap.length
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
- {/* ... rest of anchor details rendering ... */}
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 correction = content.data.corrections.find(
90
+ const wordCorrection = content.data.corrections.find(
68
91
  c => c.original_word === word
69
92
  )
70
- return correction ? correction.corrected_word : word
93
+ return wordCorrection ? wordCorrection.corrected_word : word
71
94
  }).join(' ')}"`}
72
95
  />
73
- {/* ... rest of gap details rendering ... */}
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 && window.confirm('Are you sure you want to delete this segment?')) {
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<'genius' | 'spotify'>('genius')
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
- singleSourceMatches={{
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={{ mb: 3 }}>
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?: 'genius' | 'spotify'
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] === highlightInfo.referenceIndices?.[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 && anchor.reference_positions[currentSource] === highlightInfo.referenceIndices?.[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: 'genius' | 'spotify'
5
- onSourceChange: (source: 'genius' | 'spotify') => void
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
- <Button
12
- size="small"
13
- variant={currentSource === 'genius' ? 'contained' : 'outlined'}
14
- onClick={() => onSourceChange('genius')}
15
- sx={{ mr: 1 }}
16
- >
17
- Genius
18
- </Button>
19
- <Button
20
- size="small"
21
- variant={currentSource === 'spotify' ? 'contained' : 'outlined'}
22
- onClick={() => onSourceChange('spotify')}
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?: 'genius' | 'spotify'
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: 'genius' | 'spotify'
85
- onSourceChange: (source: 'genius' | 'spotify') => void
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?: 'genius' | 'spotify'
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: 'genius' | 'spotify'
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: 'genius' | 'spotify'
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
- spotify?: string[]
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