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.
- lyrics_transcriber/core/controller.py +10 -1
- lyrics_transcriber/correction/corrector.py +4 -3
- lyrics_transcriber/frontend/dist/assets/index-CQCER5Fo.js +181 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/src/App.tsx +6 -2
- lyrics_transcriber/frontend/src/api.ts +9 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +155 -0
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +1 -1
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +23 -191
- lyrics_transcriber/frontend/src/components/EditModal.tsx +407 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +255 -221
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +39 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +35 -264
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +232 -0
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +315 -0
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +116 -138
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +116 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +243 -0
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +28 -0
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +52 -0
- lyrics_transcriber/frontend/src/components/{constants.ts → shared/constants.ts} +1 -0
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +137 -0
- lyrics_transcriber/frontend/src/components/{styles.ts → shared/styles.ts} +1 -1
- lyrics_transcriber/frontend/src/components/shared/types.ts +99 -0
- lyrics_transcriber/frontend/src/components/shared/utils/newlineCalculator.ts +37 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +76 -0
- lyrics_transcriber/frontend/src/types.ts +2 -43
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/lyrics/spotify.py +11 -0
- lyrics_transcriber/output/generator.py +28 -11
- lyrics_transcriber/review/server.py +38 -12
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/RECORD +37 -24
- lyrics_transcriber/frontend/dist/assets/index-DqFgiUni.js +0 -245
- lyrics_transcriber/frontend/src/components/DebugPanel.tsx +0 -311
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/WHEEL +0 -0
- {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
|
-
}
|
File without changes
|
File without changes
|
{lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/entry_points.txt
RENAMED
File without changes
|