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.
Files changed (51) 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-ZCT0s9MG.js} +10174 -6197
  8. lyrics_transcriber/frontend/dist/assets/index-ZCT0s9MG.js.map +1 -0
  9. lyrics_transcriber/frontend/dist/index.html +1 -1
  10. lyrics_transcriber/frontend/src/App.tsx +5 -5
  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/EditActionBar.tsx +68 -0
  16. lyrics_transcriber/frontend/src/components/EditModal.tsx +467 -399
  17. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +373 -0
  18. lyrics_transcriber/frontend/src/components/EditWordList.tsx +308 -0
  19. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  20. lyrics_transcriber/frontend/src/components/Header.tsx +141 -101
  21. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +569 -107
  22. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +22 -13
  23. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +1 -0
  24. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +29 -12
  25. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +21 -4
  26. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -15
  27. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +36 -18
  28. lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
  29. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +89 -41
  30. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +9 -2
  31. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +27 -3
  32. lyrics_transcriber/frontend/src/components/shared/types.ts +17 -2
  33. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +90 -19
  34. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +192 -0
  35. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +267 -0
  36. lyrics_transcriber/frontend/src/main.tsx +7 -1
  37. lyrics_transcriber/frontend/src/theme.ts +177 -0
  38. lyrics_transcriber/frontend/src/types.ts +1 -1
  39. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  40. lyrics_transcriber/lyrics/base_lyrics_provider.py +2 -2
  41. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  42. lyrics_transcriber/output/generator.py +40 -12
  43. lyrics_transcriber/review/server.py +238 -8
  44. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/METADATA +3 -2
  45. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/RECORD +48 -40
  46. lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +0 -1
  47. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +0 -252
  48. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +0 -110
  49. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/LICENSE +0 -0
  50. {lyrics_transcriber-0.43.1.dist-info → lyrics_transcriber-0.45.0.dist-info}/WHEEL +0 -0
  51. {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
- <App />
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;
@@ -136,4 +136,4 @@ export interface HighlightInfo {
136
136
  correction?: WordCorrection
137
137
  }
138
138
 
139
- export type InteractionMode = 'highlight' | 'details' | 'edit'
139
+ export type InteractionMode = 'highlight' | 'edit'
@@ -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/detailsmodal.tsx","./src/components/editmodal.tsx","./src/components/fileupload.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/wordeditcontrols.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/types/global.d.ts"],"version":"5.6.3"}
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.debug(f"Initializing OutputGenerator with config: {self.config}")
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