lyrics-transcriber 0.43.1__py3-none-any.whl → 0.45.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 +58 -24
- lyrics_transcriber/correction/anchor_sequence.py +22 -8
- lyrics_transcriber/correction/corrector.py +47 -3
- lyrics_transcriber/correction/handlers/llm.py +15 -12
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
- lyrics_transcriber/frontend/dist/assets/{index-D0Gr3Ep7.js → index-ZCT0s9MG.js} +10174 -6197
- lyrics_transcriber/frontend/dist/assets/index-ZCT0s9MG.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/src/App.tsx +5 -5
- lyrics_transcriber/frontend/src/api.ts +37 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +14 -10
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +62 -56
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
- lyrics_transcriber/frontend/src/components/EditModal.tsx +467 -399
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +373 -0
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +308 -0
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +141 -101
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +569 -107
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +22 -13
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +1 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +29 -12
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +21 -4
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -15
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +36 -18
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +89 -41
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +9 -2
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +27 -3
- lyrics_transcriber/frontend/src/components/shared/types.ts +17 -2
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +90 -19
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +192 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +267 -0
- lyrics_transcriber/frontend/src/main.tsx +7 -1
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types.ts +1 -1
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/lyrics/base_lyrics_provider.py +2 -2
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/generator.py +40 -12
- lyrics_transcriber/review/server.py +238 -8
- {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/METADATA +3 -2
- {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/RECORD +48 -40
- lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +0 -1
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +0 -252
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +0 -110
- {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,267 @@
|
|
1
|
+
import { useState, useCallback, useEffect, useRef } from 'react'
|
2
|
+
import { LyricsSegment, Word } from '../types'
|
3
|
+
|
4
|
+
interface UseManualSyncProps {
|
5
|
+
editedSegment: LyricsSegment | null
|
6
|
+
currentTime: number
|
7
|
+
onPlaySegment?: (startTime: number) => void
|
8
|
+
updateSegment: (words: Word[]) => void
|
9
|
+
}
|
10
|
+
|
11
|
+
// Constants for tap detection
|
12
|
+
const TAP_THRESHOLD_MS = 200 // If spacebar is pressed for less than this time, it's considered a tap
|
13
|
+
const DEFAULT_WORD_DURATION = 1.0 // Default duration in seconds when tapping
|
14
|
+
const OVERLAP_BUFFER = 0.01 // Buffer to prevent word overlap (10ms)
|
15
|
+
|
16
|
+
export default function useManualSync({
|
17
|
+
editedSegment,
|
18
|
+
currentTime,
|
19
|
+
onPlaySegment,
|
20
|
+
updateSegment
|
21
|
+
}: UseManualSyncProps) {
|
22
|
+
const [isManualSyncing, setIsManualSyncing] = useState(false)
|
23
|
+
const [syncWordIndex, setSyncWordIndex] = useState<number>(-1)
|
24
|
+
const currentTimeRef = useRef(currentTime)
|
25
|
+
const [isSpacebarPressed, setIsSpacebarPressed] = useState(false)
|
26
|
+
const wordStartTimeRef = useRef<number | null>(null)
|
27
|
+
const wordsRef = useRef<Word[]>([])
|
28
|
+
const spacebarPressTimeRef = useRef<number | null>(null)
|
29
|
+
|
30
|
+
// Keep currentTimeRef up to date
|
31
|
+
useEffect(() => {
|
32
|
+
currentTimeRef.current = currentTime
|
33
|
+
}, [currentTime])
|
34
|
+
|
35
|
+
// Keep wordsRef up to date
|
36
|
+
useEffect(() => {
|
37
|
+
if (editedSegment) {
|
38
|
+
wordsRef.current = [...editedSegment.words]
|
39
|
+
}
|
40
|
+
}, [editedSegment])
|
41
|
+
|
42
|
+
const cleanupManualSync = useCallback(() => {
|
43
|
+
setIsManualSyncing(false)
|
44
|
+
setSyncWordIndex(-1)
|
45
|
+
setIsSpacebarPressed(false)
|
46
|
+
wordStartTimeRef.current = null
|
47
|
+
spacebarPressTimeRef.current = null
|
48
|
+
}, [])
|
49
|
+
|
50
|
+
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
51
|
+
if (e.code !== 'Space') return
|
52
|
+
|
53
|
+
console.log('useManualSync - Spacebar pressed down', {
|
54
|
+
isManualSyncing,
|
55
|
+
hasEditedSegment: !!editedSegment,
|
56
|
+
syncWordIndex,
|
57
|
+
currentTime: currentTimeRef.current
|
58
|
+
})
|
59
|
+
|
60
|
+
e.preventDefault()
|
61
|
+
e.stopPropagation()
|
62
|
+
|
63
|
+
if (isManualSyncing && editedSegment && !isSpacebarPressed) {
|
64
|
+
const currentWord = syncWordIndex < editedSegment.words.length ? editedSegment.words[syncWordIndex] : null
|
65
|
+
console.log('useManualSync - Recording word start time', {
|
66
|
+
wordIndex: syncWordIndex,
|
67
|
+
wordText: currentWord?.text,
|
68
|
+
time: currentTimeRef.current
|
69
|
+
})
|
70
|
+
|
71
|
+
setIsSpacebarPressed(true)
|
72
|
+
|
73
|
+
// Record the start time of the current word
|
74
|
+
wordStartTimeRef.current = currentTimeRef.current
|
75
|
+
|
76
|
+
// Record when the spacebar was pressed (for tap detection)
|
77
|
+
spacebarPressTimeRef.current = Date.now()
|
78
|
+
|
79
|
+
// Update the word's start time immediately
|
80
|
+
if (syncWordIndex < editedSegment.words.length) {
|
81
|
+
const newWords = [...wordsRef.current]
|
82
|
+
const currentWord = newWords[syncWordIndex]
|
83
|
+
|
84
|
+
// Set the start time for the current word
|
85
|
+
currentWord.start_time = currentTimeRef.current
|
86
|
+
|
87
|
+
// Update our ref
|
88
|
+
wordsRef.current = newWords
|
89
|
+
|
90
|
+
// Update the segment
|
91
|
+
updateSegment(newWords)
|
92
|
+
}
|
93
|
+
} else if (!isManualSyncing && editedSegment && onPlaySegment) {
|
94
|
+
console.log('useManualSync - Handling segment playback')
|
95
|
+
// Toggle segment playback when not in manual sync mode
|
96
|
+
const startTime = editedSegment.start_time ?? 0
|
97
|
+
const endTime = editedSegment.end_time ?? 0
|
98
|
+
|
99
|
+
if (currentTimeRef.current >= startTime && currentTimeRef.current <= endTime) {
|
100
|
+
if (window.toggleAudioPlayback) {
|
101
|
+
window.toggleAudioPlayback()
|
102
|
+
}
|
103
|
+
} else {
|
104
|
+
onPlaySegment(startTime)
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}, [isManualSyncing, editedSegment, syncWordIndex, onPlaySegment, updateSegment, isSpacebarPressed])
|
108
|
+
|
109
|
+
const handleKeyUp = useCallback((e: KeyboardEvent) => {
|
110
|
+
if (e.code !== 'Space') return
|
111
|
+
|
112
|
+
console.log('useManualSync - Spacebar released', {
|
113
|
+
isManualSyncing,
|
114
|
+
hasEditedSegment: !!editedSegment,
|
115
|
+
syncWordIndex,
|
116
|
+
currentTime: currentTimeRef.current,
|
117
|
+
wordStartTime: wordStartTimeRef.current
|
118
|
+
})
|
119
|
+
|
120
|
+
e.preventDefault()
|
121
|
+
e.stopPropagation()
|
122
|
+
|
123
|
+
if (isManualSyncing && editedSegment && isSpacebarPressed) {
|
124
|
+
const currentWord = syncWordIndex < editedSegment.words.length ? editedSegment.words[syncWordIndex] : null
|
125
|
+
const pressDuration = spacebarPressTimeRef.current ? Date.now() - spacebarPressTimeRef.current : 0
|
126
|
+
const isTap = pressDuration < TAP_THRESHOLD_MS
|
127
|
+
|
128
|
+
console.log('useManualSync - Recording word end time', {
|
129
|
+
wordIndex: syncWordIndex,
|
130
|
+
wordText: currentWord?.text,
|
131
|
+
startTime: wordStartTimeRef.current,
|
132
|
+
endTime: currentTimeRef.current,
|
133
|
+
pressDuration: `${pressDuration}ms`,
|
134
|
+
isTap,
|
135
|
+
duration: currentWord ? (currentTimeRef.current - (wordStartTimeRef.current || 0)).toFixed(2) + 's' : 'N/A'
|
136
|
+
})
|
137
|
+
|
138
|
+
setIsSpacebarPressed(false)
|
139
|
+
|
140
|
+
if (syncWordIndex < editedSegment.words.length) {
|
141
|
+
const newWords = [...wordsRef.current]
|
142
|
+
const currentWord = newWords[syncWordIndex]
|
143
|
+
|
144
|
+
// Set the end time for the current word based on whether it was a tap or hold
|
145
|
+
if (isTap) {
|
146
|
+
// For a tap, set a default duration
|
147
|
+
const defaultEndTime = (wordStartTimeRef.current || currentTimeRef.current) + DEFAULT_WORD_DURATION
|
148
|
+
currentWord.end_time = defaultEndTime
|
149
|
+
console.log('useManualSync - Tap detected, setting default duration', {
|
150
|
+
defaultEndTime,
|
151
|
+
duration: DEFAULT_WORD_DURATION
|
152
|
+
})
|
153
|
+
} else {
|
154
|
+
// For a hold, use the current time as the end time
|
155
|
+
currentWord.end_time = currentTimeRef.current
|
156
|
+
}
|
157
|
+
|
158
|
+
// Update our ref
|
159
|
+
wordsRef.current = newWords
|
160
|
+
|
161
|
+
// Move to the next word
|
162
|
+
if (syncWordIndex === editedSegment.words.length - 1) {
|
163
|
+
// If this was the last word, finish manual sync
|
164
|
+
console.log('useManualSync - Completed manual sync for all words')
|
165
|
+
setIsManualSyncing(false)
|
166
|
+
setSyncWordIndex(-1)
|
167
|
+
wordStartTimeRef.current = null
|
168
|
+
spacebarPressTimeRef.current = null
|
169
|
+
} else {
|
170
|
+
// Otherwise, move to the next word
|
171
|
+
const nextWord = editedSegment.words[syncWordIndex + 1]
|
172
|
+
console.log('useManualSync - Moving to next word', {
|
173
|
+
nextWordIndex: syncWordIndex + 1,
|
174
|
+
nextWordText: nextWord?.text
|
175
|
+
})
|
176
|
+
setSyncWordIndex(syncWordIndex + 1)
|
177
|
+
}
|
178
|
+
|
179
|
+
// Update the segment
|
180
|
+
updateSegment(newWords)
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}, [isManualSyncing, editedSegment, syncWordIndex, updateSegment, isSpacebarPressed])
|
184
|
+
|
185
|
+
// Add a handler for when the next word starts to adjust previous word's end time if needed
|
186
|
+
useEffect(() => {
|
187
|
+
if (isManualSyncing && editedSegment && syncWordIndex > 0) {
|
188
|
+
const newWords = [...wordsRef.current]
|
189
|
+
const prevWord = newWords[syncWordIndex - 1]
|
190
|
+
const currentWord = newWords[syncWordIndex]
|
191
|
+
|
192
|
+
// If the previous word's end time overlaps with the current word's start time,
|
193
|
+
// adjust the previous word's end time
|
194
|
+
if (prevWord && currentWord &&
|
195
|
+
prevWord.end_time !== null && currentWord.start_time !== null &&
|
196
|
+
prevWord.end_time > currentWord.start_time) {
|
197
|
+
|
198
|
+
console.log('useManualSync - Adjusting previous word end time to prevent overlap', {
|
199
|
+
prevWordIndex: syncWordIndex - 1,
|
200
|
+
prevWordText: prevWord.text,
|
201
|
+
prevWordEndTime: prevWord.end_time,
|
202
|
+
currentWordStartTime: currentWord.start_time,
|
203
|
+
newEndTime: currentWord.start_time - OVERLAP_BUFFER
|
204
|
+
})
|
205
|
+
|
206
|
+
prevWord.end_time = currentWord.start_time - OVERLAP_BUFFER
|
207
|
+
|
208
|
+
// Update our ref
|
209
|
+
wordsRef.current = newWords
|
210
|
+
|
211
|
+
// Update the segment
|
212
|
+
updateSegment(newWords)
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}, [syncWordIndex, isManualSyncing, editedSegment, updateSegment])
|
216
|
+
|
217
|
+
// Combine the key handlers into a single function for external use
|
218
|
+
const handleSpacebar = useCallback((e: KeyboardEvent) => {
|
219
|
+
if (e.type === 'keydown') {
|
220
|
+
handleKeyDown(e)
|
221
|
+
} else if (e.type === 'keyup') {
|
222
|
+
handleKeyUp(e)
|
223
|
+
}
|
224
|
+
}, [handleKeyDown, handleKeyUp])
|
225
|
+
|
226
|
+
const startManualSync = useCallback(() => {
|
227
|
+
if (isManualSyncing) {
|
228
|
+
cleanupManualSync()
|
229
|
+
return
|
230
|
+
}
|
231
|
+
|
232
|
+
if (!editedSegment || !onPlaySegment) return
|
233
|
+
|
234
|
+
// Make sure we have the latest words
|
235
|
+
wordsRef.current = [...editedSegment.words]
|
236
|
+
|
237
|
+
setIsManualSyncing(true)
|
238
|
+
setSyncWordIndex(0)
|
239
|
+
setIsSpacebarPressed(false)
|
240
|
+
wordStartTimeRef.current = null
|
241
|
+
spacebarPressTimeRef.current = null
|
242
|
+
// Start playing 3 seconds before segment start
|
243
|
+
onPlaySegment((editedSegment.start_time ?? 0) - 3)
|
244
|
+
}, [isManualSyncing, editedSegment, onPlaySegment, cleanupManualSync])
|
245
|
+
|
246
|
+
// Auto-stop sync if we go past the end time
|
247
|
+
useEffect(() => {
|
248
|
+
if (!editedSegment) return
|
249
|
+
|
250
|
+
const endTime = editedSegment.end_time ?? 0
|
251
|
+
|
252
|
+
if (window.isAudioPlaying && currentTimeRef.current > endTime) {
|
253
|
+
console.log('Stopping playback: current time exceeded end time')
|
254
|
+
window.toggleAudioPlayback?.()
|
255
|
+
cleanupManualSync()
|
256
|
+
}
|
257
|
+
}, [isManualSyncing, editedSegment, currentTimeRef, cleanupManualSync])
|
258
|
+
|
259
|
+
return {
|
260
|
+
isManualSyncing,
|
261
|
+
syncWordIndex,
|
262
|
+
startManualSync,
|
263
|
+
cleanupManualSync,
|
264
|
+
handleSpacebar,
|
265
|
+
isSpacebarPressed
|
266
|
+
}
|
267
|
+
}
|
@@ -1,6 +1,12 @@
|
|
1
1
|
import ReactDOM from 'react-dom/client'
|
2
|
+
import { ThemeProvider } from '@mui/material/styles'
|
3
|
+
import CssBaseline from '@mui/material/CssBaseline'
|
2
4
|
import App from './App'
|
5
|
+
import theme from './theme'
|
3
6
|
|
4
7
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
5
|
-
<
|
8
|
+
<ThemeProvider theme={theme}>
|
9
|
+
<CssBaseline />
|
10
|
+
<App />
|
11
|
+
</ThemeProvider>
|
6
12
|
)
|
@@ -0,0 +1,177 @@
|
|
1
|
+
import { createTheme } from '@mui/material/styles';
|
2
|
+
|
3
|
+
// Create a theme with smaller typography and spacing
|
4
|
+
const theme = createTheme({
|
5
|
+
typography: {
|
6
|
+
// Scale down all typography by about 20%
|
7
|
+
fontSize: 14, // Default is 16
|
8
|
+
h1: {
|
9
|
+
fontSize: '2.5rem', // Default is ~3rem
|
10
|
+
},
|
11
|
+
h2: {
|
12
|
+
fontSize: '2rem', // Default is ~2.5rem
|
13
|
+
},
|
14
|
+
h3: {
|
15
|
+
fontSize: '1.5rem', // Default is ~1.75rem
|
16
|
+
},
|
17
|
+
h4: {
|
18
|
+
fontSize: '1.2rem', // Default is ~1.5rem
|
19
|
+
marginBottom: '0.5rem',
|
20
|
+
},
|
21
|
+
h5: {
|
22
|
+
fontSize: '1rem', // Default is ~1.25rem
|
23
|
+
},
|
24
|
+
h6: {
|
25
|
+
fontSize: '0.9rem', // Default is ~1.1rem
|
26
|
+
marginBottom: '0.5rem',
|
27
|
+
},
|
28
|
+
body1: {
|
29
|
+
fontSize: '0.85rem', // Default is ~1rem
|
30
|
+
},
|
31
|
+
body2: {
|
32
|
+
fontSize: '0.75rem', // Default is ~0.875rem
|
33
|
+
},
|
34
|
+
button: {
|
35
|
+
fontSize: '0.8rem', // Default is ~0.875rem
|
36
|
+
},
|
37
|
+
caption: {
|
38
|
+
fontSize: '0.7rem', // Default is ~0.75rem
|
39
|
+
},
|
40
|
+
},
|
41
|
+
components: {
|
42
|
+
MuiButton: {
|
43
|
+
styleOverrides: {
|
44
|
+
root: {
|
45
|
+
padding: '3px 10px', // Further reduced from 4px 12px
|
46
|
+
minHeight: '30px', // Further reduced from 32px
|
47
|
+
},
|
48
|
+
sizeSmall: {
|
49
|
+
padding: '1px 6px', // Further reduced from 2px 8px
|
50
|
+
minHeight: '24px', // Further reduced from 28px
|
51
|
+
},
|
52
|
+
},
|
53
|
+
},
|
54
|
+
MuiIconButton: {
|
55
|
+
styleOverrides: {
|
56
|
+
root: {
|
57
|
+
padding: '4px', // Further reduced from 6px
|
58
|
+
},
|
59
|
+
sizeSmall: {
|
60
|
+
padding: '2px', // Further reduced from 4px
|
61
|
+
},
|
62
|
+
},
|
63
|
+
},
|
64
|
+
MuiTextField: {
|
65
|
+
styleOverrides: {
|
66
|
+
root: {
|
67
|
+
'& .MuiInputBase-root': {
|
68
|
+
minHeight: '32px', // Further reduced from 36px
|
69
|
+
},
|
70
|
+
},
|
71
|
+
},
|
72
|
+
},
|
73
|
+
MuiDialog: {
|
74
|
+
styleOverrides: {
|
75
|
+
paper: {
|
76
|
+
padding: '8px', // Further reduced from 12px
|
77
|
+
},
|
78
|
+
},
|
79
|
+
},
|
80
|
+
MuiDialogTitle: {
|
81
|
+
styleOverrides: {
|
82
|
+
root: {
|
83
|
+
padding: '8px 12px', // Further reduced from 12px 16px
|
84
|
+
},
|
85
|
+
},
|
86
|
+
},
|
87
|
+
MuiDialogContent: {
|
88
|
+
styleOverrides: {
|
89
|
+
root: {
|
90
|
+
padding: '6px 12px', // Further reduced from 8px 16px
|
91
|
+
},
|
92
|
+
},
|
93
|
+
},
|
94
|
+
MuiDialogActions: {
|
95
|
+
styleOverrides: {
|
96
|
+
root: {
|
97
|
+
padding: '6px 12px', // Further reduced from 8px 16px
|
98
|
+
},
|
99
|
+
},
|
100
|
+
},
|
101
|
+
MuiPaper: {
|
102
|
+
styleOverrides: {
|
103
|
+
root: {
|
104
|
+
padding: '8px', // Further reduced from 12px
|
105
|
+
},
|
106
|
+
},
|
107
|
+
},
|
108
|
+
MuiList: {
|
109
|
+
styleOverrides: {
|
110
|
+
root: {
|
111
|
+
padding: '2px 0', // Further reduced from 4px 0
|
112
|
+
},
|
113
|
+
},
|
114
|
+
},
|
115
|
+
MuiListItem: {
|
116
|
+
styleOverrides: {
|
117
|
+
root: {
|
118
|
+
padding: '2px 8px', // Further reduced from 4px 12px
|
119
|
+
},
|
120
|
+
},
|
121
|
+
},
|
122
|
+
MuiTableCell: {
|
123
|
+
styleOverrides: {
|
124
|
+
root: {
|
125
|
+
padding: '4px 8px', // Further reduced from 8px 12px
|
126
|
+
},
|
127
|
+
},
|
128
|
+
},
|
129
|
+
MuiCard: {
|
130
|
+
styleOverrides: {
|
131
|
+
root: {
|
132
|
+
padding: '8px',
|
133
|
+
},
|
134
|
+
},
|
135
|
+
},
|
136
|
+
MuiCardContent: {
|
137
|
+
styleOverrides: {
|
138
|
+
root: {
|
139
|
+
padding: '8px',
|
140
|
+
'&:last-child': {
|
141
|
+
paddingBottom: '8px',
|
142
|
+
},
|
143
|
+
},
|
144
|
+
},
|
145
|
+
},
|
146
|
+
MuiCardHeader: {
|
147
|
+
styleOverrides: {
|
148
|
+
root: {
|
149
|
+
padding: '8px',
|
150
|
+
},
|
151
|
+
},
|
152
|
+
},
|
153
|
+
MuiCardActions: {
|
154
|
+
styleOverrides: {
|
155
|
+
root: {
|
156
|
+
padding: '4px 8px',
|
157
|
+
},
|
158
|
+
},
|
159
|
+
},
|
160
|
+
MuiGrid: {
|
161
|
+
styleOverrides: {
|
162
|
+
container: {
|
163
|
+
marginTop: '-4px',
|
164
|
+
marginLeft: '-4px',
|
165
|
+
width: 'calc(100% + 8px)',
|
166
|
+
},
|
167
|
+
item: {
|
168
|
+
paddingTop: '4px',
|
169
|
+
paddingLeft: '4px',
|
170
|
+
},
|
171
|
+
},
|
172
|
+
},
|
173
|
+
},
|
174
|
+
spacing: (factor: number) => `${0.6 * factor}rem`, // Further reduced from 0.8 * factor
|
175
|
+
});
|
176
|
+
|
177
|
+
export default theme;
|
@@ -1 +1 @@
|
|
1
|
-
{"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/validation.ts","./src/vite-env.d.ts","./src/components/audioplayer.tsx","./src/components/correctionmetrics.tsx","./src/components/
|
1
|
+
{"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/theme.ts","./src/types.ts","./src/validation.ts","./src/vite-env.d.ts","./src/components/addlyricsmodal.tsx","./src/components/audioplayer.tsx","./src/components/correctionmetrics.tsx","./src/components/editactionbar.tsx","./src/components/editmodal.tsx","./src/components/edittimelinesection.tsx","./src/components/editwordlist.tsx","./src/components/fileupload.tsx","./src/components/findreplacemodal.tsx","./src/components/header.tsx","./src/components/lyricsanalyzer.tsx","./src/components/modeselector.tsx","./src/components/previewvideosection.tsx","./src/components/referenceview.tsx","./src/components/reviewchangesmodal.tsx","./src/components/segmentdetailsmodal.tsx","./src/components/timelineeditor.tsx","./src/components/transcriptionview.tsx","./src/components/worddivider.tsx","./src/components/shared/constants.ts","./src/components/shared/styles.ts","./src/components/shared/types.ts","./src/components/shared/components/highlightedtext.tsx","./src/components/shared/components/sourceselector.tsx","./src/components/shared/components/word.tsx","./src/components/shared/hooks/usewordclick.ts","./src/components/shared/utils/keyboardhandlers.ts","./src/components/shared/utils/localstorage.ts","./src/components/shared/utils/referencelinecalculator.ts","./src/components/shared/utils/segmentoperations.ts","./src/components/shared/utils/wordutils.ts","./src/hooks/usemanualsync.ts","./src/types/global.d.ts"],"version":"5.6.3"}
|
@@ -47,14 +47,14 @@ class BaseLyricsProvider(ABC):
|
|
47
47
|
converted_cache_path = self._get_cache_path(cache_key, "converted")
|
48
48
|
converted_data = self._load_from_cache(converted_cache_path)
|
49
49
|
if converted_data:
|
50
|
-
self.logger.info(f"Using cached converted lyrics for {artist} - {title}")
|
50
|
+
self.logger.info(f"Using cached converted lyrics for {artist} - {title} from file: {converted_cache_path}")
|
51
51
|
return LyricsData.from_dict(converted_data)
|
52
52
|
|
53
53
|
# Check raw cache next
|
54
54
|
raw_cache_path = self._get_cache_path(cache_key, "raw")
|
55
55
|
raw_data = self._load_from_cache(raw_cache_path)
|
56
56
|
if raw_data:
|
57
|
-
self.logger.info(f"Using cached raw lyrics for {artist} - {title}")
|
57
|
+
self.logger.info(f"Using cached raw lyrics for {artist} - {title} from file: {raw_cache_path}")
|
58
58
|
converted_result = self._convert_result_format(raw_data)
|
59
59
|
self._save_to_cache(converted_cache_path, converted_result.to_dict())
|
60
60
|
return converted_result
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from typing import Optional, Dict, Any
|
2
|
+
from lyrics_transcriber.lyrics.base_lyrics_provider import BaseLyricsProvider, LyricsProviderConfig
|
3
|
+
from lyrics_transcriber.types import LyricsData, LyricsMetadata
|
4
|
+
|
5
|
+
|
6
|
+
class UserInputProvider(BaseLyricsProvider):
|
7
|
+
"""Provider for manually input lyrics text."""
|
8
|
+
|
9
|
+
def __init__(self, lyrics_text: str, source_name: str, metadata: Dict[str, Any], *args, **kwargs):
|
10
|
+
"""Initialize with the user's input text."""
|
11
|
+
super().__init__(LyricsProviderConfig(), *args, **kwargs)
|
12
|
+
self.lyrics_text = lyrics_text
|
13
|
+
self.source_name = source_name
|
14
|
+
self.input_metadata = metadata
|
15
|
+
|
16
|
+
def _fetch_data_from_source(self, artist: str, title: str) -> Optional[Dict[str, Any]]:
|
17
|
+
"""Return the user's input text as raw data."""
|
18
|
+
return {"text": self.lyrics_text, "metadata": self.input_metadata}
|
19
|
+
|
20
|
+
def _convert_result_format(self, raw_data: Dict[str, Any]) -> LyricsData:
|
21
|
+
"""Convert the raw text into LyricsData format."""
|
22
|
+
# Create segments with words from the text
|
23
|
+
segments = self._create_segments_with_words(raw_data["text"])
|
24
|
+
|
25
|
+
# Create metadata
|
26
|
+
metadata = LyricsMetadata(
|
27
|
+
source=self.source_name,
|
28
|
+
track_name=raw_data["metadata"].get("title", ""),
|
29
|
+
artist_names=raw_data["metadata"].get("artist", ""),
|
30
|
+
is_synced=False,
|
31
|
+
lyrics_provider="manual",
|
32
|
+
lyrics_provider_id="",
|
33
|
+
album_name=None,
|
34
|
+
duration_ms=None,
|
35
|
+
explicit=None,
|
36
|
+
language=None,
|
37
|
+
provider_metadata={},
|
38
|
+
)
|
39
|
+
|
40
|
+
return LyricsData(segments=segments, metadata=metadata, source=self.source_name)
|
41
|
+
|
42
|
+
def get_name(self) -> str:
|
43
|
+
"""Return the provider name."""
|
44
|
+
return "UserInput"
|
@@ -37,6 +37,7 @@ class OutputGenerator:
|
|
37
37
|
self,
|
38
38
|
config: OutputConfig,
|
39
39
|
logger: Optional[logging.Logger] = None,
|
40
|
+
preview_mode: bool = False,
|
40
41
|
):
|
41
42
|
"""
|
42
43
|
Initialize OutputGenerator with configuration.
|
@@ -44,20 +45,12 @@ class OutputGenerator:
|
|
44
45
|
Args:
|
45
46
|
config: OutputConfig instance with required paths and settings
|
46
47
|
logger: Optional logger instance
|
48
|
+
preview_mode: Boolean indicating if the generator is in preview mode
|
47
49
|
"""
|
48
50
|
self.config = config
|
49
51
|
self.logger = logger or logging.getLogger(__name__)
|
50
52
|
|
51
|
-
self.logger.
|
52
|
-
|
53
|
-
# Set video resolution parameters
|
54
|
-
self.video_resolution_num, self.font_size, self.line_height = self._get_video_params(self.config.video_resolution)
|
55
|
-
|
56
|
-
self.segment_resizer = SegmentResizer(max_line_length=self.config.max_line_length, logger=self.logger)
|
57
|
-
|
58
|
-
# Initialize generators
|
59
|
-
self.plain_text = PlainTextGenerator(self.config.output_dir, self.logger)
|
60
|
-
self.lyrics_file = LyricsFileGenerator(self.config.output_dir, self.logger)
|
53
|
+
self.logger.info(f"Initializing OutputGenerator with config: {self.config}")
|
61
54
|
|
62
55
|
if self.config.render_video or self.config.generate_cdg:
|
63
56
|
# Load output styles from JSON
|
@@ -68,10 +61,46 @@ class OutputGenerator:
|
|
68
61
|
except Exception as e:
|
69
62
|
raise ValueError(f"Failed to load output styles file: {str(e)}")
|
70
63
|
|
64
|
+
# Set video resolution parameters
|
65
|
+
self.video_resolution_num, self.font_size, self.line_height = self._get_video_params(self.config.video_resolution)
|
66
|
+
self.logger.info(f"Video resolution: {self.video_resolution_num}, font size: {self.font_size}, line height: {self.line_height}")
|
67
|
+
|
68
|
+
self.segment_resizer = SegmentResizer(max_line_length=self.config.max_line_length, logger=self.logger)
|
69
|
+
|
70
|
+
# Initialize generators
|
71
|
+
self.plain_text = PlainTextGenerator(self.config.output_dir, self.logger)
|
72
|
+
self.lyrics_file = LyricsFileGenerator(self.config.output_dir, self.logger)
|
73
|
+
|
71
74
|
if self.config.generate_cdg:
|
72
75
|
self.cdg = CDGGenerator(self.config.output_dir, self.logger)
|
73
76
|
|
77
|
+
self.preview_mode = preview_mode
|
74
78
|
if self.config.render_video:
|
79
|
+
# Apply preview mode scaling if needed
|
80
|
+
if self.preview_mode:
|
81
|
+
# Scale down from 4K (2160p) to 360p - factor of 1/6
|
82
|
+
scale_factor = 1 / 6
|
83
|
+
|
84
|
+
# Scale down top padding for preview if it exists
|
85
|
+
if "karaoke" in self.config.styles and "top_padding" in self.config.styles["karaoke"]:
|
86
|
+
self.logger.info(f"Preview mode: Found top_padding: {self.config.styles['karaoke']['top_padding']}")
|
87
|
+
original_padding = self.config.styles["karaoke"]["top_padding"]
|
88
|
+
if original_padding is not None:
|
89
|
+
# Scale down from 4K (2160p) to 360p - factor of 1/6
|
90
|
+
self.config.styles["karaoke"]["top_padding"] = original_padding * scale_factor
|
91
|
+
self.logger.info(f"Preview mode: Scaled down top_padding to: {self.config.styles['karaoke']['top_padding']}")
|
92
|
+
|
93
|
+
# Scale down font size for preview if it exists
|
94
|
+
if "karaoke" in self.config.styles and "font_size" in self.config.styles["karaoke"]:
|
95
|
+
self.logger.info(f"Preview mode: Found font_size: {self.config.styles['karaoke']['font_size']}")
|
96
|
+
original_font_size = self.config.styles["karaoke"]["font_size"]
|
97
|
+
if original_font_size is not None:
|
98
|
+
# Scale down from 4K (2160p) to 360p - factor of 1/6
|
99
|
+
self.font_size = original_font_size * scale_factor
|
100
|
+
self.config.styles["karaoke"]["font_size"] = self.font_size
|
101
|
+
self.logger.info(f"Preview mode: Scaled down font_size to: {self.font_size}")
|
102
|
+
|
103
|
+
# Initialize subtitle generator with potentially scaled values
|
75
104
|
self.subtitle = SubtitlesGenerator(
|
76
105
|
output_dir=self.config.output_dir,
|
77
106
|
video_resolution=self.video_resolution_num,
|
@@ -102,7 +131,6 @@ class OutputGenerator:
|
|
102
131
|
audio_filepath: str,
|
103
132
|
artist: Optional[str] = None,
|
104
133
|
title: Optional[str] = None,
|
105
|
-
preview_mode: bool = False,
|
106
134
|
) -> OutputPaths:
|
107
135
|
"""Generate all requested output formats."""
|
108
136
|
outputs = OutputPaths()
|
@@ -116,7 +144,7 @@ class OutputGenerator:
|
|
116
144
|
transcription_corrected.resized_segments = resized_segments
|
117
145
|
|
118
146
|
# For preview, we only need to generate ASS and video
|
119
|
-
if preview_mode:
|
147
|
+
if self.preview_mode:
|
120
148
|
# Generate ASS subtitles for preview
|
121
149
|
outputs.ass = self.subtitle.generate_ass(transcription_corrected.resized_segments, output_prefix, audio_filepath)
|
122
150
|
|