lyrics-transcriber 0.36.1__py3-none-any.whl → 0.37.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 (35) hide show
  1. lyrics_transcriber/core/controller.py +22 -2
  2. lyrics_transcriber/correction/corrector.py +8 -8
  3. lyrics_transcriber/correction/handlers/base.py +4 -0
  4. lyrics_transcriber/correction/handlers/extend_anchor.py +9 -0
  5. lyrics_transcriber/correction/handlers/no_space_punct_match.py +21 -10
  6. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +21 -11
  7. lyrics_transcriber/correction/handlers/syllables_match.py +4 -4
  8. lyrics_transcriber/correction/handlers/word_count_match.py +19 -10
  9. lyrics_transcriber/frontend/dist/assets/index-BNNbsbVN.js +182 -0
  10. lyrics_transcriber/frontend/dist/index.html +1 -1
  11. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +1 -2
  12. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +76 -70
  13. lyrics_transcriber/frontend/src/components/EditModal.tsx +10 -2
  14. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +128 -125
  15. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +1 -3
  16. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +24 -12
  17. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +8 -15
  18. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +3 -3
  19. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +34 -52
  20. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +39 -31
  21. lyrics_transcriber/frontend/src/components/shared/types.ts +3 -3
  22. lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +146 -0
  23. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +23 -24
  24. lyrics_transcriber/frontend/src/types.ts +25 -15
  25. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  26. lyrics_transcriber/output/cdg.py +32 -6
  27. lyrics_transcriber/output/video.py +17 -7
  28. lyrics_transcriber/review/server.py +24 -8
  29. {lyrics_transcriber-0.36.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/METADATA +1 -1
  30. {lyrics_transcriber-0.36.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/RECORD +33 -33
  31. {lyrics_transcriber-0.36.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/entry_points.txt +1 -0
  32. lyrics_transcriber/frontend/dist/assets/index-ztlAYPYT.js +0 -181
  33. lyrics_transcriber/frontend/src/components/shared/utils/newlineCalculator.ts +0 -37
  34. {lyrics_transcriber-0.36.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/LICENSE +0 -0
  35. {lyrics_transcriber-0.36.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/WHEEL +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-ztlAYPYT.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BNNbsbVN.js"></script>
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -73,7 +73,7 @@ interface CorrectionMetricsProps {
73
73
  correctedGapCount?: number
74
74
  uncorrectedGapCount?: number
75
75
  uncorrectedGaps?: Array<{
76
- position: number
76
+ position: string
77
77
  length: number
78
78
  }>
79
79
  // Correction details
@@ -106,7 +106,6 @@ export default function CorrectionMetrics({
106
106
  const anchorPercentage = totalWords > 0 ? Math.round((anchorWordCount / totalWords) * 100) : 0
107
107
  const uncorrectedWordCount = uncorrectedGaps?.reduce((sum, gap) => sum + gap.length, 0) ?? 0
108
108
  const uncorrectedPercentage = totalWords > 0 ? Math.round((uncorrectedWordCount / totalWords) * 100) : 0
109
- // For corrected percentage, we'll use the total affected words
110
109
  const correctedWordCount = replacedCount + addedCount
111
110
  const correctedPercentage = totalWords > 0 ?
112
111
  Math.round((correctedWordCount / totalWords) * 100) : 0
@@ -4,7 +4,8 @@ import {
4
4
  DialogContent,
5
5
  IconButton,
6
6
  Grid,
7
- Typography
7
+ Typography,
8
+ Box
8
9
  } from '@mui/material'
9
10
  import CloseIcon from '@mui/icons-material/Close'
10
11
  import { ModalContent } from './LyricsAnalyzer'
@@ -31,12 +32,9 @@ export default function DetailsModal({
31
32
  return ''
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
35
+ const isCorrected = content.type === 'gap' && content.data.corrections?.length > 0
39
36
 
37
+ const renderContent = () => {
40
38
  switch (content.type) {
41
39
  case 'anchor':
42
40
  return (
@@ -49,27 +47,31 @@ export default function DetailsModal({
49
47
  title="Full Text"
50
48
  value={`"${content.data.text}"`}
51
49
  />
52
- <GridItem title="Position" value={content.data.position} />
50
+ <GridItem title="Word ID" value={content.data.wordId} />
53
51
  <GridItem title="Length" value={`${content.data.length} words`} />
54
52
  <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
- </>
53
+ title="Reference Word IDs"
54
+ value={content.data.reference_word_ids ?
55
+ Object.entries(content.data.reference_word_ids).map(([source, ids]) => (
56
+ `${source}: ${ids?.join(', ') ?? 'No IDs'}`
57
+ )).join('\n') : 'No reference word IDs'
70
58
  }
71
59
  />
72
- <GridItem title="Total Score" value={content.data.total_score.toFixed(3)} />
60
+ <GridItem title="Confidence" value={content.data.confidence?.toFixed(3) ?? 'N/A'} />
61
+ {content.data.phrase_score && (
62
+ <GridItem
63
+ title="Phrase Score Details"
64
+ value={
65
+ <>
66
+ <Typography>Type: {content.data.phrase_score.phrase_type}</Typography>
67
+ <Typography>Natural Break: {content.data.phrase_score.natural_break_score?.toFixed(3) ?? 'N/A'}</Typography>
68
+ <Typography>Length: {content.data.phrase_score.length_score?.toFixed(3) ?? 'N/A'}</Typography>
69
+ <Typography>Total: {content.data.phrase_score.total_score?.toFixed(3) ?? 'N/A'}</Typography>
70
+ </>
71
+ }
72
+ />
73
+ )}
74
+ <GridItem title="Total Score" value={content.data.total_score?.toFixed(3) ?? 'N/A'} />
73
75
  </Grid>
74
76
  )
75
77
 
@@ -80,22 +82,18 @@ export default function DetailsModal({
80
82
  title="Selected Word"
81
83
  value={`"${getCurrentWord()}"`}
82
84
  />
83
- <GridItem
84
- title="Transcribed Text"
85
- value={`"${content.data.text}"`}
86
- />
87
85
  <GridItem
88
86
  title="Current Text"
89
- value={`"${content.data.words.map(word => {
90
- const wordCorrection = content.data.corrections.find(
87
+ value={`"${content.data.words?.map(word => {
88
+ const wordCorrection = content.data.corrections?.find(
91
89
  c => c.original_word === word
92
90
  )
93
91
  return wordCorrection ? wordCorrection.corrected_word : word
94
- }).join(' ')}"`}
92
+ }).join(' ') ?? content.data.word}"`}
95
93
  />
96
94
  <GridItem
97
- title="Position"
98
- value={content.data.position}
95
+ title="Word ID"
96
+ value={content.data.wordId}
99
97
  />
100
98
  <GridItem
101
99
  title="Length"
@@ -107,6 +105,10 @@ export default function DetailsModal({
107
105
  value={`"${content.data.preceding_anchor.text}"`}
108
106
  />
109
107
  )}
108
+ <GridItem
109
+ title="Transcribed Text"
110
+ value={`"${content.data.text}"`}
111
+ />
110
112
  {content.data.following_anchor && (
111
113
  <GridItem
112
114
  title="Following Anchor"
@@ -118,48 +120,51 @@ export default function DetailsModal({
118
120
  title="Reference Words"
119
121
  value={
120
122
  <>
121
- {Object.entries(content.data.reference_words).map(([source, words]) => (
122
- <Typography key={source}>
123
- {source}: "{words.join(' ')}"
124
- </Typography>
125
- ))}
123
+ {content.data.reference_words.spotify && (
124
+ <Typography>Spotify: "{content.data.reference_words.spotify.join(' ')}"</Typography>
125
+ )}
126
+ {content.data.reference_words.genius && (
127
+ <Typography>Genius: "{content.data.reference_words.genius.join(' ')}"</Typography>
128
+ )}
126
129
  </>
127
130
  }
128
131
  />
129
132
  )}
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
- </>
133
+ {isCorrected && content.data.corrections && (
134
+ <GridItem
135
+ title="Correction Details"
136
+ value={
137
+ <>
138
+ {content.data.corrections.map((correction, index) => (
139
+ <Box key={index} sx={{ mb: 2, p: 1, border: '1px solid #ccc', borderRadius: '4px' }}>
140
+ <Typography variant="subtitle2" fontWeight="bold">Correction {index + 1}</Typography>
141
+ <Typography>Original: <strong>"{correction.original_word}"</strong></Typography>
142
+ <Typography>Corrected: <strong>"{correction.corrected_word}"</strong></Typography>
143
+ <Typography>Word ID: {correction.word_id}</Typography>
144
+ <Typography>Confidence: {correction.confidence?.toFixed(3) ?? 'N/A'}</Typography>
145
+ <Typography>Source: {correction.source}</Typography>
146
+ <Typography>Reason: {correction.reason}</Typography>
147
+ {correction.is_deletion && <Typography>Is Deletion: Yes</Typography>}
148
+ {correction.split_total && (
149
+ <Typography>Split: {correction.split_index} of {correction.split_total}</Typography>
150
+ )}
151
+ {correction.alternatives && Object.entries(correction.alternatives).length > 0 && (
152
+ <>
153
+ <Typography>Alternatives:</Typography>
154
+ <Box sx={{ pl: 2 }}>
155
+ {Object.entries(correction.alternatives).map(([word, score]) => (
156
+ <Typography key={word}>
157
+ "{word}": {score?.toFixed(3) ?? 'N/A'}
158
+ </Typography>
159
+ ))}
160
+ </Box>
161
+ </>
162
+ )}
163
+ </Box>
164
+ ))}
165
+ </>
166
+ }
167
+ />
163
168
  )}
164
169
  </Grid>
165
170
  )
@@ -187,6 +192,7 @@ export default function DetailsModal({
187
192
  <CloseIcon />
188
193
  </IconButton>
189
194
  <DialogTitle>
195
+ {content.type === 'gap' && (isCorrected ? 'Corrected ' : 'Uncorrected ')}
190
196
  {content.type.charAt(0).toUpperCase() + content.type.slice(1)} Details - "{getCurrentWord()}"
191
197
  </DialogTitle>
192
198
  <DialogContent dividers>{renderContent()}</DialogContent>
@@ -23,6 +23,7 @@ import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'
23
23
  import { LyricsSegment, Word } from '../types'
24
24
  import { useState, useEffect } from 'react'
25
25
  import TimelineEditor from './TimelineEditor'
26
+ import { nanoid } from 'nanoid'
26
27
 
27
28
  interface EditModalProps {
28
29
  open: boolean
@@ -91,6 +92,7 @@ export default function EditModal({
91
92
  // Add at end
92
93
  const lastWord = newWords[newWords.length - 1]
93
94
  newWord = {
95
+ id: nanoid(),
94
96
  text: '',
95
97
  start_time: lastWord.end_time,
96
98
  end_time: lastWord.end_time + 0.5,
@@ -106,6 +108,7 @@ export default function EditModal({
106
108
  (nextWord ? nextWord.start_time - 0.5 : 0)
107
109
 
108
110
  newWord = {
111
+ id: nanoid(),
109
112
  text: '',
110
113
  start_time: midTime - 0.25,
111
114
  end_time: midTime + 0.25,
@@ -133,12 +136,14 @@ export default function EditModal({
133
136
  const newWords = [...editedSegment.words]
134
137
  newWords.splice(index, 1,
135
138
  {
139
+ id: nanoid(),
136
140
  text: words[0],
137
141
  start_time: word.start_time,
138
142
  end_time: midTime,
139
143
  confidence: 1.0
140
144
  },
141
145
  {
146
+ id: nanoid(),
142
147
  text: words[1],
143
148
  start_time: midTime,
144
149
  end_time: word.end_time,
@@ -157,6 +162,7 @@ export default function EditModal({
157
162
  const newWords = [...editedSegment.words]
158
163
 
159
164
  newWords.splice(index, 2, {
165
+ id: nanoid(),
160
166
  text: `${word1.text} ${word2.text}`.trim(),
161
167
  start_time: word1.start_time,
162
168
  end_time: word2.end_time,
@@ -208,17 +214,19 @@ export default function EditModal({
208
214
  let updatedWords: Word[]
209
215
 
210
216
  if (newWords.length === editedSegment.words.length) {
211
- // If word count matches, keep original timestamps
217
+ // If word count matches, keep original timestamps and IDs
212
218
  updatedWords = editedSegment.words.map((word, index) => ({
219
+ id: word.id, // Keep original ID
213
220
  text: newWords[index],
214
221
  start_time: word.start_time,
215
222
  end_time: word.end_time,
216
223
  confidence: 1.0
217
224
  }))
218
225
  } else {
219
- // If word count differs, distribute time evenly
226
+ // If word count differs, distribute time evenly and generate new IDs
220
227
  const avgWordDuration = segmentDuration / newWords.length
221
228
  updatedWords = newWords.map((text, index) => ({
229
+ id: nanoid(), // Generate new ID
222
230
  text,
223
231
  start_time: editedSegment.start_time + (index * avgWordDuration),
224
232
  end_time: editedSegment.start_time + ((index + 1) * avgWordDuration),