lyrics-transcriber 0.34.2__py3-none-any.whl → 0.35.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.
Files changed (39) hide show
  1. lyrics_transcriber/core/controller.py +10 -1
  2. lyrics_transcriber/correction/corrector.py +4 -3
  3. lyrics_transcriber/frontend/dist/assets/index-CQCER5Fo.js +181 -0
  4. lyrics_transcriber/frontend/dist/index.html +1 -1
  5. lyrics_transcriber/frontend/src/App.tsx +6 -2
  6. lyrics_transcriber/frontend/src/api.ts +9 -0
  7. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +155 -0
  8. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +1 -1
  9. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +23 -191
  10. lyrics_transcriber/frontend/src/components/EditModal.tsx +407 -0
  11. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +255 -221
  12. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +39 -0
  13. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +35 -264
  14. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +232 -0
  15. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
  16. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +315 -0
  17. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +116 -138
  18. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +116 -0
  19. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +243 -0
  20. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +28 -0
  21. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +52 -0
  22. lyrics_transcriber/frontend/src/components/{constants.ts → shared/constants.ts} +1 -0
  23. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +137 -0
  24. lyrics_transcriber/frontend/src/components/{styles.ts → shared/styles.ts} +1 -1
  25. lyrics_transcriber/frontend/src/components/shared/types.ts +99 -0
  26. lyrics_transcriber/frontend/src/components/shared/utils/newlineCalculator.ts +37 -0
  27. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +76 -0
  28. lyrics_transcriber/frontend/src/types.ts +2 -43
  29. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  30. lyrics_transcriber/lyrics/spotify.py +11 -0
  31. lyrics_transcriber/output/generator.py +28 -11
  32. lyrics_transcriber/review/server.py +38 -12
  33. {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/METADATA +1 -1
  34. {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/RECORD +37 -24
  35. lyrics_transcriber/frontend/dist/assets/index-DqFgiUni.js +0 -245
  36. lyrics_transcriber/frontend/src/components/DebugPanel.tsx +0 -311
  37. {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/LICENSE +0 -0
  38. {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/WHEEL +0 -0
  39. {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/entry_points.txt +0 -0
@@ -1,311 +0,0 @@
1
- import { Box, Typography, Accordion, AccordionSummary, AccordionDetails } from '@mui/material'
2
- import ContentCopyIcon from '@mui/icons-material/ContentCopy'
3
- import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
4
- import { CorrectionData } from '../types'
5
- import { useMemo, useRef, useState } from 'react'
6
-
7
- export interface AnchorMatchInfo {
8
- segment: string
9
- lastWord: string
10
- normalizedLastWord: string
11
- overlappingAnchors: Array<{
12
- text: string
13
- range: [number, number]
14
- words: string[]
15
- hasMatchingWord: boolean
16
- }>
17
- matchingGap: {
18
- text: string
19
- position: number
20
- length: number
21
- corrections: Array<{
22
- word: string
23
- referencePosition: number | undefined
24
- }>
25
- followingAnchor: {
26
- text: string
27
- position: number | undefined
28
- } | null
29
- } | null
30
- highlightDebug?: Array<{
31
- wordIndex: number
32
- refPos: number | undefined
33
- highlightPos: number | undefined
34
- anchorLength: number
35
- isInRange: boolean
36
- }>
37
- wordPositionDebug?: {
38
- anchorWords: string[]
39
- wordIndex: number
40
- referencePosition: number
41
- finalPosition: number
42
- }
43
- debugLog?: string[]
44
- }
45
-
46
- interface DebugPanelProps {
47
- data: CorrectionData
48
- currentSource: 'genius' | 'spotify'
49
- anchorMatchInfo: AnchorMatchInfo[]
50
- }
51
-
52
- export default function DebugPanel({ data, currentSource, anchorMatchInfo }: DebugPanelProps) {
53
- // Create a ref to hold the content div
54
- const contentRef = useRef<HTMLDivElement>(null)
55
- const [expanded, setExpanded] = useState(false)
56
-
57
- // Calculate newline positions for reference text using useMemo
58
- const { newlineInfo, newlineIndices } = useMemo(() => {
59
- const newlineInfo = new Map<number, string>()
60
- const newlineIndices = new Set(
61
- data.corrected_segments.slice(0, -1).map((segment, segmentIndex) => {
62
- const segmentWords = segment.text.trim().split(/\s+/)
63
- const lastWord = segmentWords[segmentWords.length - 1]
64
-
65
- const matchingScoredAnchor = data.anchor_sequences.find(anchor => {
66
- const transcriptionStart = anchor.transcription_position
67
- const transcriptionEnd = transcriptionStart + anchor.length - 1
68
- const lastWordPosition = data.corrected_segments
69
- .slice(0, segmentIndex)
70
- .reduce((acc, s) => acc + s.text.trim().split(/\s+/).length, 0) + segmentWords.length - 1
71
-
72
- return lastWordPosition >= transcriptionStart && lastWordPosition <= transcriptionEnd
73
- })
74
-
75
- if (!matchingScoredAnchor) {
76
- console.warn(`Could not find anchor for segment end: "${segment.text.trim()}"`)
77
- return null
78
- }
79
-
80
- const refPosition = matchingScoredAnchor.reference_positions[currentSource]
81
- if (refPosition === undefined) return null
82
-
83
- const wordOffsetInAnchor = matchingScoredAnchor.words.indexOf(lastWord)
84
- const finalPosition = refPosition + wordOffsetInAnchor
85
-
86
- newlineInfo.set(finalPosition, segment.text.trim())
87
- return finalPosition
88
- }).filter((pos): pos is number => pos !== null)
89
- )
90
- return { newlineInfo, newlineIndices }
91
- }, [data.corrected_segments, data.anchor_sequences, currentSource])
92
-
93
- // Memoize the first 5 segments data
94
- const firstFiveSegmentsData = useMemo(() =>
95
- data.corrected_segments.slice(0, 5).map((segment, i) => {
96
- const segmentWords = segment.text.trim().split(/\s+/)
97
- const previousWords = data.corrected_segments
98
- .slice(0, i)
99
- .reduce((acc, s) => acc + s.text.trim().split(/\s+/).length, 0)
100
- const lastWordPosition = previousWords + segmentWords.length - 1
101
-
102
- const matchingScoredAnchor = data.anchor_sequences.find(anchor => {
103
- const start = anchor.transcription_position
104
- const end = start + anchor.length
105
- return lastWordPosition >= start && lastWordPosition < end
106
- })
107
-
108
- return {
109
- segment,
110
- segmentWords,
111
- previousWords,
112
- lastWordPosition,
113
- matchingAnchor: matchingScoredAnchor
114
- }
115
- }), [data.corrected_segments, data.anchor_sequences])
116
-
117
- // Memoize relevant anchors
118
- const relevantAnchors = useMemo(() =>
119
- data.anchor_sequences
120
- .filter(anchor => anchor.transcription_position < 50),
121
- [data.anchor_sequences]
122
- )
123
-
124
- // Memoize relevant gaps
125
- const relevantGaps = useMemo(() =>
126
- data.gap_sequences.filter(g => g.transcription_position < 50),
127
- [data.gap_sequences]
128
- )
129
-
130
- const handleCopy = (e: React.MouseEvent) => {
131
- e.stopPropagation() // Prevent accordion from toggling
132
-
133
- // Temporarily expand to get content
134
- setExpanded(true)
135
-
136
- // Use setTimeout to allow the content to render
137
- setTimeout(() => {
138
- if (contentRef.current) {
139
- const debugText = contentRef.current.innerText
140
- navigator.clipboard.writeText(debugText)
141
-
142
- // Restore previous state if it was collapsed
143
- setExpanded(false)
144
- }
145
- }, 100)
146
- }
147
-
148
- return (
149
- <Box sx={{ mb: 3 }}>
150
- <Accordion
151
- expanded={expanded}
152
- onChange={(_, isExpanded) => setExpanded(isExpanded)}
153
- >
154
- <AccordionSummary
155
- expandIcon={<ExpandMoreIcon />}
156
- sx={{
157
- '& .MuiAccordionSummary-content': {
158
- display: 'flex',
159
- justifyContent: 'space-between',
160
- alignItems: 'center',
161
- width: '100%'
162
- }
163
- }}
164
- >
165
- <Typography>Debug Information</Typography>
166
- <Box
167
- onClick={(e) => e.stopPropagation()} // Prevent accordion toggle
168
- sx={{ display: 'flex', alignItems: 'center', mr: 2 }}
169
- >
170
- <Typography
171
- component="span"
172
- variant="body2"
173
- sx={{
174
- display: 'flex',
175
- alignItems: 'center',
176
- cursor: 'pointer',
177
- '&:hover': { opacity: 0.7 }
178
- }}
179
- onClick={handleCopy}
180
- >
181
- <ContentCopyIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
182
- Copy All
183
- </Typography>
184
- </Box>
185
- </AccordionSummary>
186
- <AccordionDetails>
187
- <Box ref={contentRef} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
188
- {/* Debug Logs */}
189
- <Box>
190
- <Typography variant="h6" gutterBottom>Debug Logs (first 5 segments)</Typography>
191
- <Typography component="pre" sx={{
192
- fontSize: '0.75rem',
193
- whiteSpace: 'pre-wrap',
194
- backgroundColor: '#f5f5f5',
195
- padding: 2,
196
- borderRadius: 1
197
- }}>
198
- {anchorMatchInfo.slice(0, 5).map((info, i) =>
199
- `Segment ${i + 1}: "${info.segment}"\n` +
200
- (info.debugLog ? info.debugLog.map(log => ` ${log}`).join('\n') : ' No debug logs\n')
201
- ).join('\n')}
202
- </Typography>
203
- </Box>
204
-
205
- {/* First 5 Segments */}
206
- <Box>
207
- <Typography variant="h6" gutterBottom>First 5 Segments (with position details)</Typography>
208
- <Typography component="pre" sx={{ fontSize: '0.75rem', whiteSpace: 'pre-wrap' }}>
209
- {firstFiveSegmentsData.map(({ segment, segmentWords, previousWords, lastWordPosition, matchingAnchor }, index) => {
210
- return `Segment ${index + 1}: "${segment.text.trim()}"\n` +
211
- ` Words: ${segment.words.length} (${segmentWords.length} after trimming)\n` +
212
- ` Word count before segment: ${previousWords}\n` +
213
- ` Last word position: ${lastWordPosition}\n` +
214
- ` Matching anchor: ${matchingAnchor ?
215
- `"${matchingAnchor.text}"\n Position: ${matchingAnchor.transcription_position}\n` +
216
- ` Length: ${matchingAnchor.length}\n` +
217
- ` Reference positions: genius=${matchingAnchor.reference_positions.genius}, spotify=${matchingAnchor.reference_positions.spotify}`
218
- : 'None'}\n`
219
- }).join('\n')}
220
- </Typography>
221
- </Box>
222
-
223
- {/* Relevant Anchors */}
224
- <Box>
225
- <Typography variant="h6" gutterBottom>Relevant Anchors</Typography>
226
- <Typography component="pre" sx={{ fontSize: '0.75rem', whiteSpace: 'pre-wrap' }}>
227
- {relevantAnchors.map((anchor, i) => {
228
- return `Anchor ${i}: "${anchor.text}"\n` +
229
- ` Position: ${anchor.transcription_position}\n` +
230
- ` Length: ${anchor.length}\n` +
231
- ` Words: ${anchor.words.join(' ')}\n` +
232
- ` Reference Positions: genius=${anchor.reference_positions.genius}, spotify=${anchor.reference_positions.spotify}\n`
233
- }).join('\n')}
234
- </Typography>
235
- </Box>
236
-
237
- {/* Relevant Gaps */}
238
- <Box>
239
- <Typography variant="h6" gutterBottom>Relevant Gaps</Typography>
240
- <Typography component="pre" sx={{ fontSize: '0.75rem', whiteSpace: 'pre-wrap' }}>
241
- {relevantGaps.map((gap, i) => {
242
- return `Gap ${i}: "${gap.text}"\n` +
243
- ` Position: ${gap.transcription_position}\n` +
244
- ` Length: ${gap.length}\n` +
245
- ` Words: ${gap.words.join(' ')}\n` +
246
- ` Corrections: ${gap.corrections.length}\n`
247
- }).join('\n')}
248
- </Typography>
249
- </Box>
250
-
251
- {/* First 5 Newlines */}
252
- <Box>
253
- <Typography variant="h6" gutterBottom>First 5 Newlines (with detailed anchor matching)</Typography>
254
- <Typography component="pre" sx={{ fontSize: '0.75rem', whiteSpace: 'pre-wrap' }}>
255
- {Array.from(newlineIndices).sort((a, b) => a - b).slice(0, 5).map(pos => {
256
- const matchingAnchor = data.anchor_sequences.find(anchor => {
257
- const start = anchor.reference_positions[currentSource]
258
- const end = start + anchor.length
259
- return pos >= start && pos < end
260
- })
261
-
262
- const matchingSegment = data.corrected_segments.find(segment =>
263
- newlineInfo.get(pos) === segment.text.trim()
264
- )
265
-
266
- const segmentIndex = matchingSegment ? data.corrected_segments.indexOf(matchingSegment) : -1
267
- const lastWord = matchingSegment?.text.trim().split(/\s+/).pop()
268
- const wordIndex = matchingAnchor?.words.indexOf(lastWord ?? '') ?? -1
269
- const expectedPosition = matchingAnchor && wordIndex !== -1 ?
270
- matchingAnchor.reference_positions[currentSource] + wordIndex :
271
- 'Unknown'
272
-
273
- return `Position ${pos}: "${newlineInfo.get(pos)}"\n` +
274
- ` In Anchor: ${matchingAnchor ? `"${matchingAnchor.text}"` : 'None'}\n` +
275
- ` Anchor Position: ${matchingAnchor?.reference_positions[currentSource]}\n` +
276
- ` Matching Segment Index: ${segmentIndex}\n` +
277
- ` Expected Position in Reference: ${expectedPosition}\n`
278
- }).join('\n')}
279
- </Typography>
280
- </Box>
281
-
282
- {/* Anchor Matching Debug section */}
283
- <Box>
284
- <Typography variant="h6" gutterBottom>Anchor Matching Debug (first 5 segments)</Typography>
285
- <Typography component="pre" sx={{ fontSize: '0.75rem', whiteSpace: 'pre-wrap' }}>
286
- {anchorMatchInfo.slice(0, 5).map((info, i) => `
287
- Segment ${i}: "${info.segment}"
288
- Last word: "${info.lastWord}" (normalized: "${info.normalizedLastWord}")
289
- Debug Log:
290
- ${info.debugLog ? info.debugLog.map(log => ` ${log}`).join('\n') : ' none'}
291
- Overlapping anchors:
292
- ${info.overlappingAnchors.map(anchor => ` "${anchor.text}"
293
- Range: ${anchor.range[0]}-${anchor.range[1]}
294
- Words: ${anchor.words.join(', ')}
295
- Has matching word: ${anchor.hasMatchingWord}
296
- `).join('\n')}
297
- Word Position Debug: ${info.wordPositionDebug ?
298
- `\n Anchor words: ${info.wordPositionDebug.anchorWords.join(', ')}
299
- Word index in anchor: ${info.wordPositionDebug.wordIndex}
300
- Reference position: ${info.wordPositionDebug.referencePosition}
301
- Final position: ${info.wordPositionDebug.finalPosition}`
302
- : 'none'}
303
- `).join('\n')}
304
- </Typography>
305
- </Box>
306
- </Box>
307
- </AccordionDetails>
308
- </Accordion>
309
- </Box>
310
- )
311
- }