lyrics-transcriber 0.41.0__py3-none-any.whl → 0.42.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 (77) hide show
  1. lyrics_transcriber/core/controller.py +30 -52
  2. lyrics_transcriber/correction/anchor_sequence.py +325 -150
  3. lyrics_transcriber/correction/corrector.py +224 -107
  4. lyrics_transcriber/correction/handlers/base.py +28 -10
  5. lyrics_transcriber/correction/handlers/extend_anchor.py +47 -24
  6. lyrics_transcriber/correction/handlers/levenshtein.py +75 -33
  7. lyrics_transcriber/correction/handlers/llm.py +290 -0
  8. lyrics_transcriber/correction/handlers/no_space_punct_match.py +81 -36
  9. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +46 -26
  10. lyrics_transcriber/correction/handlers/repeat.py +28 -11
  11. lyrics_transcriber/correction/handlers/sound_alike.py +68 -32
  12. lyrics_transcriber/correction/handlers/syllables_match.py +80 -30
  13. lyrics_transcriber/correction/handlers/word_count_match.py +36 -19
  14. lyrics_transcriber/correction/handlers/word_operations.py +68 -22
  15. lyrics_transcriber/correction/text_utils.py +3 -7
  16. lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
  17. lyrics_transcriber/frontend/.yarn/releases/yarn-4.6.0.cjs +934 -0
  18. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  19. lyrics_transcriber/frontend/dist/assets/{index-DKnNJHRK.js → index-coH8y7gV.js} +16284 -9032
  20. lyrics_transcriber/frontend/dist/assets/index-coH8y7gV.js.map +1 -0
  21. lyrics_transcriber/frontend/dist/index.html +1 -1
  22. lyrics_transcriber/frontend/package.json +6 -2
  23. lyrics_transcriber/frontend/src/App.tsx +18 -2
  24. lyrics_transcriber/frontend/src/api.ts +103 -6
  25. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +7 -6
  26. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +86 -59
  27. lyrics_transcriber/frontend/src/components/EditModal.tsx +93 -43
  28. lyrics_transcriber/frontend/src/components/FileUpload.tsx +2 -2
  29. lyrics_transcriber/frontend/src/components/Header.tsx +251 -0
  30. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +303 -265
  31. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +117 -0
  32. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +125 -40
  33. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +129 -115
  34. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +59 -78
  35. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +40 -16
  36. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +4 -10
  37. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +137 -68
  38. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +1 -1
  39. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +85 -115
  40. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  41. lyrics_transcriber/frontend/src/components/shared/types.ts +15 -7
  42. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +35 -0
  43. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  44. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +7 -7
  45. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +121 -0
  46. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  47. lyrics_transcriber/frontend/src/types.js +2 -0
  48. lyrics_transcriber/frontend/src/types.ts +70 -49
  49. lyrics_transcriber/frontend/src/validation.ts +132 -0
  50. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  51. lyrics_transcriber/frontend/yarn.lock +3752 -0
  52. lyrics_transcriber/lyrics/base_lyrics_provider.py +75 -12
  53. lyrics_transcriber/lyrics/file_provider.py +6 -5
  54. lyrics_transcriber/lyrics/genius.py +5 -2
  55. lyrics_transcriber/lyrics/spotify.py +58 -21
  56. lyrics_transcriber/output/ass/config.py +16 -5
  57. lyrics_transcriber/output/cdg.py +1 -1
  58. lyrics_transcriber/output/generator.py +22 -8
  59. lyrics_transcriber/output/plain_text.py +15 -10
  60. lyrics_transcriber/output/segment_resizer.py +16 -3
  61. lyrics_transcriber/output/subtitles.py +27 -1
  62. lyrics_transcriber/output/video.py +107 -1
  63. lyrics_transcriber/review/__init__.py +0 -1
  64. lyrics_transcriber/review/server.py +337 -164
  65. lyrics_transcriber/transcribers/audioshake.py +3 -0
  66. lyrics_transcriber/transcribers/base_transcriber.py +11 -3
  67. lyrics_transcriber/transcribers/whisper.py +11 -1
  68. lyrics_transcriber/types.py +151 -105
  69. lyrics_transcriber/utils/word_utils.py +27 -0
  70. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/METADATA +3 -1
  71. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/RECORD +74 -61
  72. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/WHEEL +1 -1
  73. lyrics_transcriber/frontend/dist/assets/index-DKnNJHRK.js.map +0 -1
  74. lyrics_transcriber/frontend/package-lock.json +0 -4260
  75. lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +0 -202
  76. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/LICENSE +0 -0
  77. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,117 @@
1
+ import { Box, Typography, CircularProgress, Alert, Button } from '@mui/material'
2
+ import { useState, useEffect } from 'react'
3
+ import { ApiClient } from '../api'
4
+ import { CorrectionData } from '../types'
5
+
6
+ interface PreviewVideoSectionProps {
7
+ apiClient: ApiClient | null
8
+ isModalOpen: boolean
9
+ updatedData: CorrectionData
10
+ }
11
+
12
+ export default function PreviewVideoSection({
13
+ apiClient,
14
+ isModalOpen,
15
+ updatedData
16
+ }: PreviewVideoSectionProps) {
17
+ const [previewState, setPreviewState] = useState<{
18
+ status: 'loading' | 'ready' | 'error';
19
+ videoUrl?: string;
20
+ error?: string;
21
+ }>({ status: 'loading' });
22
+
23
+ // Generate preview when modal opens
24
+ useEffect(() => {
25
+ if (isModalOpen && apiClient) {
26
+ const generatePreview = async () => {
27
+ setPreviewState({ status: 'loading' });
28
+ try {
29
+ const response = await apiClient.generatePreviewVideo(updatedData);
30
+
31
+ if (response.status === 'error') {
32
+ setPreviewState({
33
+ status: 'error',
34
+ error: response.message || 'Failed to generate preview video'
35
+ });
36
+ return;
37
+ }
38
+
39
+ if (!response.preview_hash) {
40
+ setPreviewState({
41
+ status: 'error',
42
+ error: 'No preview hash received from server'
43
+ });
44
+ return;
45
+ }
46
+
47
+ const videoUrl = apiClient.getPreviewVideoUrl(response.preview_hash);
48
+ setPreviewState({
49
+ status: 'ready',
50
+ videoUrl
51
+ });
52
+ } catch (error) {
53
+ setPreviewState({
54
+ status: 'error',
55
+ error: (error as Error).message || 'Failed to generate preview video'
56
+ });
57
+ }
58
+ };
59
+
60
+ generatePreview();
61
+ }
62
+ }, [isModalOpen, apiClient, updatedData]);
63
+
64
+ if (!apiClient) return null;
65
+
66
+ return (
67
+ <Box sx={{ mb: 2 }}>
68
+ {previewState.status === 'loading' && (
69
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, p: 2 }}>
70
+ <CircularProgress size={24} />
71
+ <Typography>Generating preview video...</Typography>
72
+ </Box>
73
+ )}
74
+
75
+ {previewState.status === 'error' && (
76
+ <Box sx={{ mb: 2 }}>
77
+ <Alert
78
+ severity="error"
79
+ action={
80
+ <Button
81
+ color="inherit"
82
+ size="small"
83
+ onClick={() => {
84
+ // Re-trigger the effect by toggling isModalOpen
85
+ setPreviewState({ status: 'loading' });
86
+ }}
87
+ >
88
+ Retry
89
+ </Button>
90
+ }
91
+ >
92
+ {previewState.error}
93
+ </Alert>
94
+ </Box>
95
+ )}
96
+
97
+ {previewState.status === 'ready' && previewState.videoUrl && (
98
+ <Box sx={{
99
+ width: '100%',
100
+ margin: '0',
101
+ }}>
102
+ <video
103
+ controls
104
+ src={previewState.videoUrl}
105
+ style={{
106
+ display: 'block',
107
+ width: '100%',
108
+ height: 'auto',
109
+ }}
110
+ >
111
+ Your browser does not support the video tag.
112
+ </video>
113
+ </Box>
114
+ )}
115
+ </Box>
116
+ );
117
+ }
@@ -4,10 +4,11 @@ import { ReferenceViewProps } from './shared/types'
4
4
  import { calculateReferenceLinePositions } from './shared/utils/referenceLineCalculator'
5
5
  import { SourceSelector } from './shared/components/SourceSelector'
6
6
  import { HighlightedText } from './shared/components/HighlightedText'
7
- import { WordCorrection } from '@/types'
7
+ import { TranscriptionWordPosition } from './shared/types'
8
+ import { getWordsFromIds } from './shared/utils/wordUtils'
8
9
 
9
10
  export default function ReferenceView({
10
- referenceTexts,
11
+ referenceSources,
11
12
  anchors,
12
13
  onElementClick,
13
14
  onWordClick,
@@ -17,58 +18,140 @@ export default function ReferenceView({
17
18
  onSourceChange,
18
19
  highlightInfo,
19
20
  mode,
20
- gaps
21
+ gaps,
22
+ corrections
21
23
  }: ReferenceViewProps) {
22
- // Get available sources from referenceTexts object
24
+ // Get available sources from referenceSources object
23
25
  const availableSources = useMemo(() =>
24
- Object.keys(referenceTexts) as Array<string>,
25
- [referenceTexts]
26
+ Object.keys(referenceSources),
27
+ [referenceSources]
26
28
  )
27
29
 
30
+ // Ensure we always have a valid currentSource
31
+ const effectiveCurrentSource = useMemo(() =>
32
+ currentSource || (availableSources.length > 0 ? availableSources[0] : ''),
33
+ [currentSource, availableSources]
34
+ )
35
+
36
+ // Create word positions for the reference view
37
+ const referenceWordPositions = useMemo(() => {
38
+ const positions: TranscriptionWordPosition[] = [];
39
+ const allPositions = new Map<number, TranscriptionWordPosition[]>();
40
+
41
+ // Map anchor words
42
+ anchors?.forEach(anchor => {
43
+ const position = anchor.reference_positions[effectiveCurrentSource];
44
+ if (position === undefined) return;
45
+
46
+ if (!allPositions.has(position)) {
47
+ allPositions.set(position, []);
48
+ }
49
+
50
+ const referenceWords = getWordsFromIds(
51
+ referenceSources[effectiveCurrentSource].segments,
52
+ anchor.reference_word_ids[effectiveCurrentSource] || []
53
+ );
54
+
55
+ referenceWords.forEach(word => {
56
+ const wordPosition: TranscriptionWordPosition = {
57
+ word: {
58
+ id: word.id,
59
+ text: word.text,
60
+ start_time: word.start_time ?? undefined,
61
+ end_time: word.end_time ?? undefined
62
+ },
63
+ type: 'anchor',
64
+ sequence: anchor,
65
+ isInRange: true
66
+ };
67
+ allPositions.get(position)!.push(wordPosition);
68
+ });
69
+ });
70
+
71
+ // Map gap words
72
+ gaps?.forEach(gap => {
73
+ const precedingAnchor = gap.preceding_anchor_id ?
74
+ anchors?.find(a => a.id === gap.preceding_anchor_id) :
75
+ undefined;
76
+ const followingAnchor = gap.following_anchor_id ?
77
+ anchors?.find(a => a.id === gap.following_anchor_id) :
78
+ undefined;
79
+
80
+ const position = precedingAnchor?.reference_positions[effectiveCurrentSource] ??
81
+ followingAnchor?.reference_positions[effectiveCurrentSource];
82
+
83
+ if (position === undefined) return;
84
+
85
+ const gapPosition = precedingAnchor ? position + 1 : position - 1;
86
+
87
+ if (!allPositions.has(gapPosition)) {
88
+ allPositions.set(gapPosition, []);
89
+ }
90
+
91
+ const referenceWords = getWordsFromIds(
92
+ referenceSources[effectiveCurrentSource].segments,
93
+ gap.reference_word_ids[effectiveCurrentSource] || []
94
+ );
95
+
96
+ referenceWords.forEach(word => {
97
+ // Find if this word has a correction
98
+ const isWordCorrected = corrections?.some(correction =>
99
+ correction.reference_positions?.[effectiveCurrentSource]?.toString() === word.id &&
100
+ gap.transcribed_word_ids.includes(correction.word_id)
101
+ );
102
+
103
+ const wordPosition: TranscriptionWordPosition = {
104
+ word: {
105
+ id: word.id,
106
+ text: word.text,
107
+ start_time: word.start_time ?? undefined,
108
+ end_time: word.end_time ?? undefined
109
+ },
110
+ type: 'gap',
111
+ sequence: gap,
112
+ isInRange: true,
113
+ isCorrected: isWordCorrected
114
+ };
115
+ allPositions.get(gapPosition)!.push(wordPosition);
116
+ });
117
+ });
118
+
119
+ // Sort by position and flatten
120
+ Array.from(allPositions.entries())
121
+ .sort(([a], [b]) => a - b)
122
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
123
+ .forEach(([_, words]) => {
124
+ positions.push(...words);
125
+ });
126
+
127
+ return positions;
128
+ }, [anchors, gaps, effectiveCurrentSource, referenceSources, corrections]);
129
+
28
130
  const { linePositions } = useMemo(() =>
29
131
  calculateReferenceLinePositions(
30
132
  corrected_segments,
31
133
  anchors,
32
- currentSource
134
+ effectiveCurrentSource
33
135
  ),
34
- [corrected_segments, anchors, currentSource]
136
+ [corrected_segments, anchors, effectiveCurrentSource]
35
137
  )
36
138
 
37
139
  // Create a mapping of reference words to their corrections
38
140
  const referenceCorrections = useMemo(() => {
39
- const corrections = new Map<string, string>();
40
-
41
- console.log('Building referenceCorrections map:', {
42
- gapsCount: gaps.length,
43
- currentSource,
44
- });
141
+ const correctionMap = new Map<string, string>();
45
142
 
46
- gaps.forEach(gap => {
47
- gap.corrections.forEach((correction: WordCorrection) => {
48
- // Get the reference position for this correction
49
- const referencePosition = correction.reference_positions?.[currentSource];
50
-
51
- if (typeof referencePosition === 'number') {
52
- const wordId = `${currentSource}-word-${referencePosition}`;
53
- corrections.set(wordId, correction.corrected_word);
54
-
55
- console.log('Adding correction mapping:', {
56
- wordId,
57
- correctedWord: correction.corrected_word,
58
- referencePosition,
59
- correction
60
- });
61
- }
62
- });
143
+ corrections?.forEach(correction => {
144
+ const referencePosition = correction.reference_positions?.[effectiveCurrentSource];
145
+ if (referencePosition !== undefined) {
146
+ correctionMap.set(referencePosition.toString(), correction.corrected_word);
147
+ }
63
148
  });
64
149
 
65
- console.log('Final referenceCorrections map:', {
66
- size: corrections.size,
67
- entries: Array.from(corrections.entries())
68
- });
150
+ return correctionMap;
151
+ }, [corrections, effectiveCurrentSource]);
69
152
 
70
- return corrections;
71
- }, [gaps, currentSource]);
153
+ // Get the segments for the current source
154
+ const currentSourceSegments = referenceSources[effectiveCurrentSource]?.segments || [];
72
155
 
73
156
  return (
74
157
  <Paper sx={{ p: 2 }}>
@@ -77,14 +160,15 @@ export default function ReferenceView({
77
160
  Reference Text
78
161
  </Typography>
79
162
  <SourceSelector
80
- currentSource={currentSource}
163
+ currentSource={effectiveCurrentSource}
81
164
  onSourceChange={onSourceChange}
82
165
  availableSources={availableSources}
83
166
  />
84
167
  </Box>
85
168
  <Box sx={{ display: 'flex', flexDirection: 'column' }}>
86
169
  <HighlightedText
87
- text={referenceTexts[currentSource]}
170
+ wordPositions={referenceWordPositions}
171
+ segments={currentSourceSegments}
88
172
  anchors={anchors}
89
173
  onElementClick={onElementClick}
90
174
  onWordClick={onWordClick}
@@ -92,10 +176,11 @@ export default function ReferenceView({
92
176
  highlightInfo={highlightInfo}
93
177
  mode={mode}
94
178
  isReference={true}
95
- currentSource={currentSource}
179
+ currentSource={effectiveCurrentSource}
96
180
  linePositions={linePositions}
97
181
  referenceCorrections={referenceCorrections}
98
182
  gaps={gaps}
183
+ preserveSegments={true}
99
184
  />
100
185
  </Box>
101
186
  </Paper>