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.
- lyrics_transcriber/core/controller.py +30 -52
- lyrics_transcriber/correction/anchor_sequence.py +325 -150
- lyrics_transcriber/correction/corrector.py +224 -107
- lyrics_transcriber/correction/handlers/base.py +28 -10
- lyrics_transcriber/correction/handlers/extend_anchor.py +47 -24
- lyrics_transcriber/correction/handlers/levenshtein.py +75 -33
- lyrics_transcriber/correction/handlers/llm.py +290 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +81 -36
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +46 -26
- lyrics_transcriber/correction/handlers/repeat.py +28 -11
- lyrics_transcriber/correction/handlers/sound_alike.py +68 -32
- lyrics_transcriber/correction/handlers/syllables_match.py +80 -30
- lyrics_transcriber/correction/handlers/word_count_match.py +36 -19
- lyrics_transcriber/correction/handlers/word_operations.py +68 -22
- lyrics_transcriber/correction/text_utils.py +3 -7
- lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.6.0.cjs +934 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/dist/assets/{index-DKnNJHRK.js → index-coH8y7gV.js} +16284 -9032
- lyrics_transcriber/frontend/dist/assets/index-coH8y7gV.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/package.json +6 -2
- lyrics_transcriber/frontend/src/App.tsx +18 -2
- lyrics_transcriber/frontend/src/api.ts +103 -6
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +7 -6
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +86 -59
- lyrics_transcriber/frontend/src/components/EditModal.tsx +93 -43
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +2 -2
- lyrics_transcriber/frontend/src/components/Header.tsx +251 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +303 -265
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +117 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +125 -40
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +129 -115
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +59 -78
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +40 -16
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +4 -10
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +137 -68
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +1 -1
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +85 -115
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +15 -7
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +35 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +7 -7
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +121 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +70 -49
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +75 -12
- lyrics_transcriber/lyrics/file_provider.py +6 -5
- lyrics_transcriber/lyrics/genius.py +5 -2
- lyrics_transcriber/lyrics/spotify.py +58 -21
- lyrics_transcriber/output/ass/config.py +16 -5
- lyrics_transcriber/output/cdg.py +1 -1
- lyrics_transcriber/output/generator.py +22 -8
- lyrics_transcriber/output/plain_text.py +15 -10
- lyrics_transcriber/output/segment_resizer.py +16 -3
- lyrics_transcriber/output/subtitles.py +27 -1
- lyrics_transcriber/output/video.py +107 -1
- lyrics_transcriber/review/__init__.py +0 -1
- lyrics_transcriber/review/server.py +337 -164
- lyrics_transcriber/transcribers/audioshake.py +3 -0
- lyrics_transcriber/transcribers/base_transcriber.py +11 -3
- lyrics_transcriber/transcribers/whisper.py +11 -1
- lyrics_transcriber/types.py +151 -105
- lyrics_transcriber/utils/word_utils.py +27 -0
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/METADATA +3 -1
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/RECORD +74 -61
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/WHEEL +1 -1
- lyrics_transcriber/frontend/dist/assets/index-DKnNJHRK.js.map +0 -1
- lyrics_transcriber/frontend/package-lock.json +0 -4260
- lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +0 -202
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/LICENSE +0 -0
- {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 {
|
7
|
+
import { TranscriptionWordPosition } from './shared/types'
|
8
|
+
import { getWordsFromIds } from './shared/utils/wordUtils'
|
8
9
|
|
9
10
|
export default function ReferenceView({
|
10
|
-
|
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
|
24
|
+
// Get available sources from referenceSources object
|
23
25
|
const availableSources = useMemo(() =>
|
24
|
-
Object.keys(
|
25
|
-
[
|
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
|
-
|
134
|
+
effectiveCurrentSource
|
33
135
|
),
|
34
|
-
[corrected_segments, anchors,
|
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
|
40
|
-
|
41
|
-
console.log('Building referenceCorrections map:', {
|
42
|
-
gapsCount: gaps.length,
|
43
|
-
currentSource,
|
44
|
-
});
|
141
|
+
const correctionMap = new Map<string, string>();
|
45
142
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
66
|
-
|
67
|
-
entries: Array.from(corrections.entries())
|
68
|
-
});
|
150
|
+
return correctionMap;
|
151
|
+
}, [corrections, effectiveCurrentSource]);
|
69
152
|
|
70
|
-
|
71
|
-
|
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={
|
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
|
-
|
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={
|
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>
|