lyrics-transcriber 0.43.1__py3-none-any.whl → 0.44.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 (49) hide show
  1. lyrics_transcriber/core/controller.py +58 -24
  2. lyrics_transcriber/correction/anchor_sequence.py +22 -8
  3. lyrics_transcriber/correction/corrector.py +47 -3
  4. lyrics_transcriber/correction/handlers/llm.py +15 -12
  5. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  6. lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
  7. lyrics_transcriber/frontend/dist/assets/{index-D0Gr3Ep7.js → index-DVoI6Z16.js} +10799 -7490
  8. lyrics_transcriber/frontend/dist/assets/index-DVoI6Z16.js.map +1 -0
  9. lyrics_transcriber/frontend/dist/index.html +1 -1
  10. lyrics_transcriber/frontend/src/App.tsx +4 -4
  11. lyrics_transcriber/frontend/src/api.ts +37 -0
  12. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  13. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +14 -10
  14. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +62 -56
  15. lyrics_transcriber/frontend/src/components/EditModal.tsx +232 -237
  16. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  17. lyrics_transcriber/frontend/src/components/GlobalSyncEditor.tsx +675 -0
  18. lyrics_transcriber/frontend/src/components/Header.tsx +141 -101
  19. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +146 -80
  20. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +22 -13
  21. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +1 -0
  22. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +29 -12
  23. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +21 -4
  24. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -15
  25. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +34 -16
  26. lyrics_transcriber/frontend/src/components/WordDivider.tsx +186 -0
  27. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +89 -41
  28. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +9 -2
  29. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +28 -3
  30. lyrics_transcriber/frontend/src/components/shared/types.ts +17 -2
  31. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +63 -14
  32. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +192 -0
  33. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +267 -0
  34. lyrics_transcriber/frontend/src/main.tsx +7 -1
  35. lyrics_transcriber/frontend/src/theme.ts +177 -0
  36. lyrics_transcriber/frontend/src/types.ts +1 -1
  37. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  38. lyrics_transcriber/lyrics/base_lyrics_provider.py +2 -2
  39. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  40. lyrics_transcriber/output/generator.py +40 -12
  41. lyrics_transcriber/review/server.py +238 -8
  42. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.44.0.dist-info}/METADATA +3 -2
  43. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.44.0.dist-info}/RECORD +46 -40
  44. lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +0 -1
  45. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +0 -252
  46. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +0 -110
  47. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.44.0.dist-info}/LICENSE +0 -0
  48. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.44.0.dist-info}/WHEEL +0 -0
  49. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.44.0.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-D0Gr3Ep7.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DVoI6Z16.js"></script>
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -183,18 +183,18 @@ export default function App() {
183
183
 
184
184
  return (
185
185
  <Box sx={{
186
- p: 3,
187
- pb: 6, // Add bottom padding to ensure content doesn't get cut off
186
+ p: 1.5,
187
+ pb: 3,
188
188
  maxWidth: '100%',
189
189
  overflowX: 'hidden'
190
190
  }}>
191
191
  {error && (
192
- <Alert severity="error" sx={{ mb: 2 }} onClose={() => setError(null)}>
192
+ <Alert severity="error" sx={{ mb: 1 }} onClose={() => setError(null)}>
193
193
  {error}
194
194
  </Alert>
195
195
  )}
196
196
  {isReadOnly && (
197
- <Alert severity="info" sx={{ mb: 2 }}>
197
+ <Alert severity="info" sx={{ mb: 1 }}>
198
198
  Running in read-only mode. Connect to an API to enable editing.
199
199
  </Alert>
200
200
  )}
@@ -10,6 +10,7 @@ export interface ApiClient {
10
10
  getPreviewVideoUrl: (previewHash: string) => string;
11
11
  updateHandlers: (enabledHandlers: string[]) => Promise<CorrectionData>;
12
12
  isUpdatingHandlers?: boolean;
13
+ addLyrics: (source: string, lyrics: string) => Promise<CorrectionData>;
13
14
  }
14
15
 
15
16
  // Add new interface for the minimal update payload
@@ -25,6 +26,12 @@ interface PreviewVideoResponse {
25
26
  message?: string;
26
27
  }
27
28
 
29
+ // Add new interface for adding lyrics
30
+ interface AddLyricsRequest {
31
+ source: string;
32
+ lyrics: string;
33
+ }
34
+
28
35
  export class LiveApiClient implements ApiClient {
29
36
  constructor(private baseUrl: string) {
30
37
  this.baseUrl = baseUrl.replace(/\/$/, '')
@@ -131,6 +138,32 @@ export class LiveApiClient implements ApiClient {
131
138
  console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
132
139
  }
133
140
  }
141
+
142
+ async addLyrics(source: string, lyrics: string): Promise<CorrectionData> {
143
+ const payload: AddLyricsRequest = {
144
+ source,
145
+ lyrics
146
+ };
147
+
148
+ const response = await fetch(`${this.baseUrl}/add-lyrics`, {
149
+ method: 'POST',
150
+ headers: {
151
+ 'Content-Type': 'application/json',
152
+ },
153
+ body: JSON.stringify(payload)
154
+ });
155
+
156
+ if (!response.ok) {
157
+ throw new Error(`API error: ${response.statusText}`);
158
+ }
159
+
160
+ const data = await response.json();
161
+ if (data.status === 'error') {
162
+ throw new Error(data.message || 'Failed to add lyrics');
163
+ }
164
+
165
+ return validateCorrectionData(data.data);
166
+ }
134
167
  }
135
168
 
136
169
  export class FileOnlyClient implements ApiClient {
@@ -161,5 +194,9 @@ export class FileOnlyClient implements ApiClient {
161
194
  async updateHandlers(_enabledHandlers: string[]): Promise<CorrectionData> {
162
195
  throw new Error('Not supported in file-only mode');
163
196
  }
197
+
198
+ async addLyrics(): Promise<CorrectionData> {
199
+ throw new Error('Not supported in file-only mode');
200
+ }
164
201
  }
165
202
 
@@ -0,0 +1,114 @@
1
+ import { useState } from 'react'
2
+ import {
3
+ Dialog,
4
+ DialogTitle,
5
+ DialogContent,
6
+ DialogActions,
7
+ Button,
8
+ TextField,
9
+ Box,
10
+ CircularProgress,
11
+ Typography
12
+ } from '@mui/material'
13
+
14
+ interface AddLyricsModalProps {
15
+ open: boolean
16
+ onClose: () => void
17
+ onSubmit: (source: string, lyrics: string) => Promise<void>
18
+ isSubmitting: boolean
19
+ }
20
+
21
+ export default function AddLyricsModal({
22
+ open,
23
+ onClose,
24
+ onSubmit,
25
+ isSubmitting
26
+ }: AddLyricsModalProps) {
27
+ const [source, setSource] = useState('')
28
+ const [lyrics, setLyrics] = useState('')
29
+ const [error, setError] = useState<string | null>(null)
30
+
31
+ const handleSubmit = async () => {
32
+ if (!source.trim()) {
33
+ setError('Please enter a source name')
34
+ return
35
+ }
36
+ if (!lyrics.trim()) {
37
+ setError('Please enter lyrics text')
38
+ return
39
+ }
40
+
41
+ try {
42
+ await onSubmit(source.trim(), lyrics.trim())
43
+ // Reset form on success
44
+ setSource('')
45
+ setLyrics('')
46
+ setError(null)
47
+ onClose()
48
+ } catch (err) {
49
+ setError(err instanceof Error ? err.message : 'Failed to add lyrics')
50
+ }
51
+ }
52
+
53
+ const handleClose = () => {
54
+ // Don't allow closing if currently submitting
55
+ if (isSubmitting) return
56
+
57
+ setSource('')
58
+ setLyrics('')
59
+ setError(null)
60
+ onClose()
61
+ }
62
+
63
+ return (
64
+ <Dialog
65
+ open={open}
66
+ onClose={handleClose}
67
+ maxWidth="md"
68
+ fullWidth
69
+ disableEscapeKeyDown={isSubmitting}
70
+ >
71
+ <DialogTitle>Add Reference Lyrics</DialogTitle>
72
+ <DialogContent>
73
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
74
+ {error && (
75
+ <Typography color="error" variant="body2">
76
+ {error}
77
+ </Typography>
78
+ )}
79
+ <TextField
80
+ label="Source Name"
81
+ value={source}
82
+ onChange={(e) => setSource(e.target.value)}
83
+ disabled={isSubmitting}
84
+ fullWidth
85
+ placeholder="e.g., Official Lyrics, Album Booklet"
86
+ />
87
+ <TextField
88
+ label="Lyrics"
89
+ value={lyrics}
90
+ onChange={(e) => setLyrics(e.target.value)}
91
+ disabled={isSubmitting}
92
+ fullWidth
93
+ multiline
94
+ rows={10}
95
+ placeholder="Paste lyrics text here (one line per segment)"
96
+ />
97
+ </Box>
98
+ </DialogContent>
99
+ <DialogActions>
100
+ <Button onClick={handleClose} disabled={isSubmitting}>
101
+ Cancel
102
+ </Button>
103
+ <Button
104
+ onClick={handleSubmit}
105
+ variant="contained"
106
+ disabled={isSubmitting}
107
+ startIcon={isSubmitting ? <CircularProgress size={20} /> : undefined}
108
+ >
109
+ {isSubmitting ? 'Adding...' : 'Add Lyrics'}
110
+ </Button>
111
+ </DialogActions>
112
+ </Dialog>
113
+ )
114
+ }
@@ -130,23 +130,24 @@ export default function AudioPlayer({ apiClient, onTimeUpdate, audioHash }: Audi
130
130
  <Box sx={{
131
131
  display: 'flex',
132
132
  alignItems: 'center',
133
- gap: 1,
133
+ gap: 0.5,
134
134
  backgroundColor: 'background.paper',
135
135
  borderRadius: 1,
136
- height: 40, // Match ToggleButtonGroup height
136
+ height: '32px',
137
137
  }}>
138
- <Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
138
+ <Typography variant="body2" color="text.secondary" sx={{ mr: 0.5, fontSize: '0.75rem' }}>
139
139
  Playback:
140
140
  </Typography>
141
141
 
142
142
  <IconButton
143
143
  onClick={handlePlayPause}
144
144
  size="small"
145
+ sx={{ p: 0.5 }}
145
146
  >
146
- {isPlaying ? <PauseIcon /> : <PlayArrowIcon />}
147
+ {isPlaying ? <PauseIcon fontSize="small" /> : <PlayArrowIcon fontSize="small" />}
147
148
  </IconButton>
148
149
 
149
- <Typography variant="body2" sx={{ minWidth: 40 }}>
150
+ <Typography variant="body2" sx={{ minWidth: 32, fontSize: '0.75rem' }}>
150
151
  {formatTime(currentTime)}
151
152
  </Typography>
152
153
 
@@ -157,16 +158,19 @@ export default function AudioPlayer({ apiClient, onTimeUpdate, audioHash }: Audi
157
158
  onChange={handleSeek}
158
159
  size="small"
159
160
  sx={{
160
- width: 200,
161
- mx: 1,
161
+ width: 150,
162
+ mx: 0.5,
162
163
  '& .MuiSlider-thumb': {
163
- width: 12,
164
- height: 12,
164
+ width: 10,
165
+ height: 10,
166
+ },
167
+ '& .MuiSlider-rail, & .MuiSlider-track': {
168
+ height: 3
165
169
  }
166
170
  }}
167
171
  />
168
172
 
169
- <Typography variant="body2" sx={{ minWidth: 40 }}>
173
+ <Typography variant="body2" sx={{ minWidth: 32, fontSize: '0.75rem' }}>
170
174
  {formatTime(duration)}
171
175
  </Typography>
172
176
  </Box>
@@ -1,4 +1,4 @@
1
- import { Grid, Paper, Box, Typography } from '@mui/material'
1
+ import { Paper, Box, Typography } from '@mui/material'
2
2
  import { COLORS } from './shared/constants'
3
3
 
4
4
  interface MetricProps {
@@ -14,46 +14,50 @@ function Metric({ color, label, value, description, details, onClick }: MetricPr
14
14
  return (
15
15
  <Paper
16
16
  sx={{
17
- p: 2,
17
+ p: 0.8,
18
+ pt: 0,
19
+ height: '100%',
18
20
  cursor: onClick ? 'pointer' : 'default',
19
21
  '&:hover': onClick ? {
20
22
  bgcolor: 'action.hover'
21
- } : undefined
23
+ } : undefined,
24
+ display: 'flex',
25
+ flexDirection: 'column'
22
26
  }}
23
27
  onClick={onClick}
24
28
  >
25
- <Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
29
+ <Box sx={{ display: 'flex', alignItems: 'center', mb: 0.5, mt: 0.8 }}>
26
30
  {color && (
27
31
  <Box
28
32
  sx={{
29
- width: 16,
30
- height: 16,
33
+ width: 12,
34
+ height: 12,
31
35
  borderRadius: 1,
32
36
  bgcolor: color,
33
- mr: 1,
37
+ mr: 0.5,
34
38
  }}
35
39
  />
36
40
  )}
37
- <Typography variant="subtitle2" color="text.secondary">
41
+ <Typography variant="subtitle2" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
38
42
  {label}
39
43
  </Typography>
40
44
  </Box>
41
- <Box sx={{ display: 'flex', alignItems: 'baseline', gap: 1, mb: 0.5 }}>
42
- <Typography variant="h6">
45
+ <Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.5, mb: 0.3 }}>
46
+ <Typography variant="h6" sx={{ fontSize: '1.1rem' }}>
43
47
  {value}
44
48
  </Typography>
45
49
  </Box>
46
- <Typography variant="caption" color="text.secondary">
50
+ <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
47
51
  {description}
48
52
  </Typography>
49
53
  {details && (
50
- <Box sx={{ mt: 1, pt: 1, borderTop: 1, borderColor: 'divider' }}>
54
+ <Box sx={{ mt: 0.5, flex: 1, overflow: 'auto' }}>
51
55
  {details.map((detail, index) => (
52
- <Box key={index} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 0.5 }}>
53
- <Typography variant="caption" color="text.secondary">
56
+ <Box key={index} sx={{ display: 'flex', justifyContent: 'space-between', mt: 0.3 }}>
57
+ <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
54
58
  {detail.label}
55
59
  </Typography>
56
- <Typography variant="caption">
60
+ <Typography variant="caption" sx={{ fontSize: '0.7rem' }}>
57
61
  {detail.value}
58
62
  </Typography>
59
63
  </Box>
@@ -111,46 +115,48 @@ export default function CorrectionMetrics({
111
115
  Math.round((correctedWordCount / totalWords) * 100) : 0
112
116
 
113
117
  return (
114
- <Grid container spacing={2}>
115
- <Grid item xs={12} sm={6} md={4}>
116
- <Metric
117
- color={COLORS.anchor}
118
- label="Anchor Sequences"
119
- value={`${anchorCount ?? '-'} (${anchorPercentage}%)`}
120
- description="Matched sections between transcription and reference"
121
- details={[
122
- { label: "Words in Anchors", value: anchorWordCount },
123
- { label: "Multi-source Matches", value: multiSourceAnchors },
124
- ]}
125
- onClick={onMetricClick?.anchor}
126
- />
127
- </Grid>
128
- <Grid item xs={12} sm={6} md={4}>
129
- <Metric
130
- color={COLORS.corrected}
131
- label="Corrected Gaps"
132
- value={`${correctedGapCount} (${correctedPercentage}%)`}
133
- description="Successfully corrected sections"
134
- details={[
135
- { label: "Words Replaced", value: replacedCount },
136
- { label: "Words Added / Deleted", value: `+${addedCount} / -${deletedCount}` },
137
- ]}
138
- onClick={onMetricClick?.corrected}
139
- />
140
- </Grid>
141
- <Grid item xs={12} sm={6} md={4}>
142
- <Metric
143
- color={COLORS.uncorrectedGap}
144
- label="Uncorrected Gaps"
145
- value={`${uncorrectedGapCount} (${uncorrectedPercentage}%)`}
146
- description="Sections that may need manual review"
147
- details={[
148
- { label: "Words Uncorrected", value: uncorrectedWordCount },
149
- { label: "Number of Gaps", value: uncorrectedGapCount },
150
- ]}
151
- onClick={onMetricClick?.uncorrected}
152
- />
153
- </Grid>
154
- </Grid>
118
+ <Box sx={{ height: '100%', display: 'flex' }}>
119
+ <Box sx={{ flex: 1, display: 'flex', flexDirection: 'row', gap: 1, height: '100%' }}>
120
+ <Box sx={{ flex: 1, height: '100%' }}>
121
+ <Metric
122
+ color={COLORS.anchor}
123
+ label="Anchor Sequences"
124
+ value={`${anchorCount ?? '-'} (${anchorPercentage}%)`}
125
+ description="Matched sections between transcription and reference"
126
+ details={[
127
+ { label: 'Words in Anchors', value: anchorWordCount },
128
+ { label: 'Multi-source Matches', value: multiSourceAnchors }
129
+ ]}
130
+ onClick={onMetricClick?.anchor}
131
+ />
132
+ </Box>
133
+ <Box sx={{ flex: 1, height: '100%' }}>
134
+ <Metric
135
+ color={COLORS.corrected}
136
+ label="Corrected Gaps"
137
+ value={`${correctedGapCount} (${correctedPercentage}%)`}
138
+ description="Successfully corrected sections"
139
+ details={[
140
+ { label: 'Words Replaced', value: replacedCount },
141
+ { label: 'Words Added / Deleted', value: `+${addedCount} / -${deletedCount}` }
142
+ ]}
143
+ onClick={onMetricClick?.corrected}
144
+ />
145
+ </Box>
146
+ <Box sx={{ flex: 1, height: '100%' }}>
147
+ <Metric
148
+ color={COLORS.uncorrectedGap}
149
+ label="Uncorrected Gaps"
150
+ value={`${uncorrectedGapCount} (${uncorrectedPercentage}%)`}
151
+ description="Sections that may need manual review"
152
+ details={[
153
+ { label: 'Words Uncorrected', value: uncorrectedWordCount },
154
+ { label: 'Number of Gaps', value: uncorrectedGaps.length }
155
+ ]}
156
+ onClick={onMetricClick?.uncorrected}
157
+ />
158
+ </Box>
159
+ </Box>
160
+ </Box>
155
161
  )
156
162
  }