lyrics-transcriber 0.35.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.
- lyrics_transcriber/cli/cli_main.py +2 -0
- lyrics_transcriber/core/config.py +1 -1
- lyrics_transcriber/core/controller.py +35 -2
- lyrics_transcriber/correction/corrector.py +8 -8
- lyrics_transcriber/correction/handlers/base.py +4 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +9 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +21 -10
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +21 -11
- lyrics_transcriber/correction/handlers/syllables_match.py +4 -4
- lyrics_transcriber/correction/handlers/word_count_match.py +19 -10
- lyrics_transcriber/frontend/dist/assets/index-BNNbsbVN.js +182 -0
- 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 -27
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +108 -12
- lyrics_transcriber/frontend/src/components/EditModal.tsx +10 -2
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +145 -141
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +7 -2
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +24 -12
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +8 -15
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +3 -3
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +36 -51
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +17 -19
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +41 -33
- lyrics_transcriber/frontend/src/components/shared/types.ts +6 -6
- lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +146 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +24 -25
- lyrics_transcriber/frontend/src/types.ts +24 -23
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/lyrics/base_lyrics_provider.py +1 -0
- lyrics_transcriber/lyrics/file_provider.py +89 -0
- lyrics_transcriber/output/cdg.py +32 -6
- lyrics_transcriber/output/video.py +17 -7
- lyrics_transcriber/review/server.py +24 -8
- {lyrics_transcriber-0.35.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.35.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/RECORD +39 -38
- {lyrics_transcriber-0.35.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/entry_points.txt +1 -0
- lyrics_transcriber/frontend/dist/assets/index-CQCER5Fo.js +0 -181
- lyrics_transcriber/frontend/src/components/shared/utils/newlineCalculator.ts +0 -37
- {lyrics_transcriber-0.35.1.dist-info → lyrics_transcriber-0.37.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.35.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-
|
8
|
+
<script type="module" crossorigin src="/assets/index-BNNbsbVN.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,21 +68,20 @@ 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
|
76
75
|
uncorrectedGaps?: Array<{
|
77
|
-
position:
|
76
|
+
position: string
|
78
77
|
length: number
|
79
78
|
}>
|
80
79
|
// Correction details
|
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,23 @@ 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
|
+
const correctedWordCount = replacedCount + addedCount
|
110
|
+
const correctedPercentage = totalWords > 0 ?
|
111
|
+
Math.round((correctedWordCount / totalWords) * 100) : 0
|
109
112
|
|
110
113
|
return (
|
111
114
|
<Grid container spacing={2}>
|
@@ -113,12 +116,11 @@ export default function CorrectionMetrics({
|
|
113
116
|
<Metric
|
114
117
|
color={COLORS.anchor}
|
115
118
|
label="Anchor Sequences"
|
116
|
-
value={anchorCount ?? '-'}
|
119
|
+
value={`${anchorCount ?? '-'} (${anchorPercentage}%)`}
|
117
120
|
description="Matched sections between transcription and reference"
|
118
121
|
details={[
|
122
|
+
{ label: "Words in Anchors", value: anchorWordCount },
|
119
123
|
{ label: "Multi-source Matches", value: multiSourceAnchors },
|
120
|
-
{ label: "Spotify Only", value: singleSourceMatches.spotify },
|
121
|
-
{ label: "Genius Only", value: singleSourceMatches.genius },
|
122
124
|
]}
|
123
125
|
onClick={onMetricClick?.anchor}
|
124
126
|
/>
|
@@ -127,12 +129,11 @@ export default function CorrectionMetrics({
|
|
127
129
|
<Metric
|
128
130
|
color={COLORS.corrected}
|
129
131
|
label="Corrected Gaps"
|
130
|
-
value={correctedGapCount
|
132
|
+
value={`${correctedGapCount} (${correctedPercentage}%)`}
|
131
133
|
description="Successfully corrected sections"
|
132
134
|
details={[
|
133
135
|
{ label: "Words Replaced", value: replacedCount },
|
134
|
-
{ label: "Words Added", value: addedCount },
|
135
|
-
{ label: "Words Deleted", value: deletedCount },
|
136
|
+
{ label: "Words Added / Deleted", value: `+${addedCount} / -${deletedCount}` },
|
136
137
|
]}
|
137
138
|
onClick={onMetricClick?.corrected}
|
138
139
|
/>
|
@@ -141,12 +142,12 @@ export default function CorrectionMetrics({
|
|
141
142
|
<Metric
|
142
143
|
color={COLORS.uncorrectedGap}
|
143
144
|
label="Uncorrected Gaps"
|
144
|
-
value={uncorrectedGapCount}
|
145
|
+
value={`${uncorrectedGapCount} (${uncorrectedPercentage}%)`}
|
145
146
|
description="Sections that may need manual review"
|
146
|
-
details={
|
147
|
-
label:
|
148
|
-
value:
|
149
|
-
}
|
147
|
+
details={[
|
148
|
+
{ label: "Words Uncorrected", value: uncorrectedWordCount },
|
149
|
+
{ label: "Number of Gaps", value: uncorrectedGapCount },
|
150
|
+
]}
|
150
151
|
onClick={onMetricClick?.uncorrected}
|
151
152
|
/>
|
152
153
|
</Grid>
|
@@ -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,6 +32,8 @@ export default function DetailsModal({
|
|
31
32
|
return ''
|
32
33
|
}
|
33
34
|
|
35
|
+
const isCorrected = content.type === 'gap' && content.data.corrections?.length > 0
|
36
|
+
|
34
37
|
const renderContent = () => {
|
35
38
|
switch (content.type) {
|
36
39
|
case 'anchor':
|
@@ -44,9 +47,31 @@ export default function DetailsModal({
|
|
44
47
|
title="Full Text"
|
45
48
|
value={`"${content.data.text}"`}
|
46
49
|
/>
|
47
|
-
<GridItem title="
|
50
|
+
<GridItem title="Word ID" value={content.data.wordId} />
|
48
51
|
<GridItem title="Length" value={`${content.data.length} words`} />
|
49
|
-
|
52
|
+
<GridItem
|
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'
|
58
|
+
}
|
59
|
+
/>
|
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'} />
|
50
75
|
</Grid>
|
51
76
|
)
|
52
77
|
|
@@ -57,20 +82,90 @@ export default function DetailsModal({
|
|
57
82
|
title="Selected Word"
|
58
83
|
value={`"${getCurrentWord()}"`}
|
59
84
|
/>
|
60
|
-
<GridItem
|
61
|
-
title="Transcribed Text"
|
62
|
-
value={`"${content.data.text}"`}
|
63
|
-
/>
|
64
85
|
<GridItem
|
65
86
|
title="Current Text"
|
66
|
-
value={`"${content.data.words
|
67
|
-
const
|
87
|
+
value={`"${content.data.words?.map(word => {
|
88
|
+
const wordCorrection = content.data.corrections?.find(
|
68
89
|
c => c.original_word === word
|
69
90
|
)
|
70
|
-
return
|
71
|
-
}).join(' ')}"`}
|
91
|
+
return wordCorrection ? wordCorrection.corrected_word : word
|
92
|
+
}).join(' ') ?? content.data.word}"`}
|
93
|
+
/>
|
94
|
+
<GridItem
|
95
|
+
title="Word ID"
|
96
|
+
value={content.data.wordId}
|
97
|
+
/>
|
98
|
+
<GridItem
|
99
|
+
title="Length"
|
100
|
+
value={`${content.data.length} words`}
|
101
|
+
/>
|
102
|
+
{content.data.preceding_anchor && (
|
103
|
+
<GridItem
|
104
|
+
title="Preceding Anchor"
|
105
|
+
value={`"${content.data.preceding_anchor.text}"`}
|
106
|
+
/>
|
107
|
+
)}
|
108
|
+
<GridItem
|
109
|
+
title="Transcribed Text"
|
110
|
+
value={`"${content.data.text}"`}
|
72
111
|
/>
|
73
|
-
{
|
112
|
+
{content.data.following_anchor && (
|
113
|
+
<GridItem
|
114
|
+
title="Following Anchor"
|
115
|
+
value={`"${content.data.following_anchor.text}"`}
|
116
|
+
/>
|
117
|
+
)}
|
118
|
+
{content.data.reference_words && (
|
119
|
+
<GridItem
|
120
|
+
title="Reference Words"
|
121
|
+
value={
|
122
|
+
<>
|
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
|
+
)}
|
129
|
+
</>
|
130
|
+
}
|
131
|
+
/>
|
132
|
+
)}
|
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
|
+
/>
|
168
|
+
)}
|
74
169
|
</Grid>
|
75
170
|
)
|
76
171
|
|
@@ -97,6 +192,7 @@ export default function DetailsModal({
|
|
97
192
|
<CloseIcon />
|
98
193
|
</IconButton>
|
99
194
|
<DialogTitle>
|
195
|
+
{content.type === 'gap' && (isCorrected ? 'Corrected ' : 'Uncorrected ')}
|
100
196
|
{content.type.charAt(0).toUpperCase() + content.type.slice(1)} Details - "{getCurrentWord()}"
|
101
197
|
</DialogTitle>
|
102
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),
|