lyrics-transcriber 0.68.0__py3-none-any.whl → 0.70.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 (20) hide show
  1. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
  2. lyrics_transcriber/frontend/package.json +1 -1
  3. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +4 -2
  4. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +257 -134
  5. lyrics_transcriber/frontend/src/components/Header.tsx +14 -0
  6. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +57 -234
  7. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +27 -3
  8. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
  9. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -18
  10. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +198 -30
  11. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  12. lyrics_transcriber/frontend/web_assets/assets/{index-D7BQUJXK.js → index-BV5ep1cr.js} +1020 -327
  13. lyrics_transcriber/frontend/web_assets/assets/index-BV5ep1cr.js.map +1 -0
  14. lyrics_transcriber/frontend/web_assets/index.html +1 -1
  15. {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.70.0.dist-info}/METADATA +1 -1
  16. {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.70.0.dist-info}/RECORD +19 -17
  17. lyrics_transcriber/frontend/web_assets/assets/index-D7BQUJXK.js.map +0 -1
  18. {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.70.0.dist-info}/LICENSE +0 -0
  19. {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.70.0.dist-info}/WHEEL +0 -0
  20. {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.70.0.dist-info}/entry_points.txt +0 -0
@@ -7,6 +7,7 @@ interface TimelineEditorProps {
7
7
  startTime: number
8
8
  endTime: number
9
9
  onWordUpdate: (index: number, updates: Partial<Word>) => void
10
+ onUnsyncWord?: (index: number) => void
10
11
  currentTime?: number
11
12
  onPlaySegment?: (time: number) => void
12
13
  showPlaybackIndicator?: boolean
@@ -114,7 +115,7 @@ const TimelineCursor = styled(Box)(({ theme }) => ({
114
115
  zIndex: 1, // Ensure it's above other elements
115
116
  }))
116
117
 
117
- export default function TimelineEditor({ words, startTime, endTime, onWordUpdate, currentTime = 0, onPlaySegment, showPlaybackIndicator = true }: TimelineEditorProps) {
118
+ export default function TimelineEditor({ words, startTime, endTime, onWordUpdate, onUnsyncWord, currentTime = 0, onPlaySegment, showPlaybackIndicator = true }: TimelineEditorProps) {
118
119
  const containerRef = useRef<HTMLDivElement>(null)
119
120
  const [dragState, setDragState] = useState<{
120
121
  wordIndex: number
@@ -157,29 +158,21 @@ export default function TimelineEditor({ words, startTime, endTime, onWordUpdate
157
158
  const position = ((time - startTime) / duration) * 100
158
159
  return Math.max(0, Math.min(100, position))
159
160
  }
160
-
161
161
  const generateTimelineMarks = () => {
162
162
  const marks = []
163
163
  const startSecond = Math.floor(startTime)
164
164
  const endSecond = Math.ceil(endTime)
165
165
 
166
- // Generate marks for each 0.1 second interval
167
- for (let time = startSecond; time <= endSecond; time += 0.1) {
166
+ // Generate marks for each second
167
+ for (let time = startSecond; time <= endSecond; time++) {
168
168
  if (time >= startTime && time <= endTime) {
169
169
  const position = timeToPosition(time)
170
- const isFullSecond = Math.abs(time - Math.round(time)) < 0.001
171
-
172
170
  marks.push(
173
171
  <Box key={time}>
174
- <TimelineMark
175
- className={isFullSecond ? '' : 'subsecond'}
176
- sx={{ left: `${position}%` }}
177
- />
178
- {isFullSecond && (
179
- <TimelineLabel sx={{ left: `${position}%` }}>
180
- {Math.round(time)}s
181
- </TimelineLabel>
182
- )}
172
+ <TimelineMark sx={{ left: `${position}%` }} />
173
+ <TimelineLabel sx={{ left: `${position}%` }}>
174
+ {time}s
175
+ </TimelineLabel>
183
176
  </Box>
184
177
  )
185
178
  }
@@ -276,6 +269,24 @@ export default function TimelineEditor({ words, startTime, endTime, onWordUpdate
276
269
  setDragState(null)
277
270
  }
278
271
 
272
+ const handleContextMenu = (e: React.MouseEvent, wordIndex: number) => {
273
+ e.preventDefault()
274
+ e.stopPropagation()
275
+
276
+ // Only unsync synced words
277
+ const word = words[wordIndex]
278
+ if (word.start_time === null || word.end_time === null) return
279
+
280
+ // Directly unsync the word without showing a menu
281
+ if (onUnsyncWord) {
282
+ console.log('TimelineEditor - Right-click unsync word', {
283
+ wordIndex,
284
+ wordText: word.text
285
+ })
286
+ onUnsyncWord(wordIndex)
287
+ }
288
+ }
289
+
279
290
  const isWordHighlighted = (word: Word): boolean => {
280
291
  if (!currentTime || word.start_time === null || word.end_time === null) return false
281
292
  return currentTime >= word.start_time && currentTime <= word.end_time
@@ -319,13 +330,12 @@ export default function TimelineEditor({ words, startTime, endTime, onWordUpdate
319
330
  )}
320
331
 
321
332
  {words.map((word, index) => {
322
- // Skip words with null timestamps
333
+ // Only render synced words on the timeline
323
334
  if (word.start_time === null || word.end_time === null) return null;
324
-
335
+
325
336
  const leftPosition = timeToPosition(word.start_time)
326
337
  const rightPosition = timeToPosition(word.end_time)
327
338
  const width = rightPosition - leftPosition
328
- // Remove the visual padding that creates gaps
329
339
  const adjustedWidth = width
330
340
 
331
341
  return (
@@ -341,6 +351,7 @@ export default function TimelineEditor({ words, startTime, endTime, onWordUpdate
341
351
  e.stopPropagation()
342
352
  handleMouseDown(e, index, 'move')
343
353
  }}
354
+ onContextMenu={(e) => handleContextMenu(e, index)}
344
355
  >
345
356
  <ResizeHandle
346
357
  className="left"
@@ -10,7 +10,7 @@ interface UseManualSyncProps {
10
10
 
11
11
  // Constants for tap detection
12
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
13
+ const DEFAULT_WORD_DURATION = 0.5 // Default duration in seconds when tapping (500ms)
14
14
  const OVERLAP_BUFFER = 0.01 // Buffer to prevent word overlap (10ms)
15
15
 
16
16
  export default function useManualSync({
@@ -20,12 +20,16 @@ export default function useManualSync({
20
20
  updateSegment
21
21
  }: UseManualSyncProps) {
22
22
  const [isManualSyncing, setIsManualSyncing] = useState(false)
23
+ const [isPaused, setIsPaused] = useState(false)
23
24
  const [syncWordIndex, setSyncWordIndex] = useState<number>(-1)
24
25
  const currentTimeRef = useRef(currentTime)
25
26
  const [isSpacebarPressed, setIsSpacebarPressed] = useState(false)
26
27
  const wordStartTimeRef = useRef<number | null>(null)
27
28
  const wordsRef = useRef<Word[]>([])
28
29
  const spacebarPressTimeRef = useRef<number | null>(null)
30
+
31
+ // Use ref to track if we need to update segment to avoid calling it too frequently
32
+ const needsSegmentUpdateRef = useRef(false)
29
33
 
30
34
  // Keep currentTimeRef up to date
31
35
  useEffect(() => {
@@ -39,14 +43,70 @@ export default function useManualSync({
39
43
  }
40
44
  }, [editedSegment])
41
45
 
46
+ // Debounced segment update to batch multiple word changes
47
+ useEffect(() => {
48
+ if (needsSegmentUpdateRef.current) {
49
+ needsSegmentUpdateRef.current = false
50
+ updateSegment(wordsRef.current)
51
+ }
52
+ }, [updateSegment, syncWordIndex]) // Only update when syncWordIndex changes
53
+
42
54
  const cleanupManualSync = useCallback(() => {
43
55
  setIsManualSyncing(false)
56
+ setIsPaused(false)
44
57
  setSyncWordIndex(-1)
45
58
  setIsSpacebarPressed(false)
46
59
  wordStartTimeRef.current = null
47
60
  spacebarPressTimeRef.current = null
61
+ needsSegmentUpdateRef.current = false
62
+
63
+ // Stop audio playback when cleaning up manual sync
64
+ if (window.toggleAudioPlayback && window.isAudioPlaying) {
65
+ window.toggleAudioPlayback()
66
+ }
48
67
  }, [])
49
68
 
69
+ const pauseManualSync = useCallback(() => {
70
+ if (isManualSyncing && !isPaused) {
71
+ console.log('useManualSync - Pausing manual sync')
72
+ setIsPaused(true)
73
+ // Pause audio playback
74
+ if (window.toggleAudioPlayback && window.isAudioPlaying) {
75
+ window.toggleAudioPlayback()
76
+ }
77
+ }
78
+ }, [isManualSyncing, isPaused])
79
+
80
+ const resumeManualSync = useCallback(() => {
81
+ if (isManualSyncing && isPaused) {
82
+ console.log('useManualSync - Resuming manual sync')
83
+ setIsPaused(false)
84
+
85
+ // Find the first unsynced word and resume from there
86
+ if (editedSegment) {
87
+ const firstUnsyncedIndex = editedSegment.words.findIndex(word =>
88
+ word.start_time === null || word.end_time === null
89
+ )
90
+
91
+ if (firstUnsyncedIndex !== -1 && firstUnsyncedIndex !== syncWordIndex) {
92
+ console.log('useManualSync - Resuming from first unsynced word', {
93
+ previousIndex: syncWordIndex,
94
+ newIndex: firstUnsyncedIndex,
95
+ wordText: editedSegment.words[firstUnsyncedIndex]?.text
96
+ })
97
+ setSyncWordIndex(firstUnsyncedIndex)
98
+ } else {
99
+ console.log('useManualSync - Resuming from current position', { syncWordIndex })
100
+ }
101
+ }
102
+
103
+ // Resume audio playback if we have an onPlaySegment function
104
+ if (onPlaySegment && currentTimeRef.current !== undefined) {
105
+ onPlaySegment(currentTimeRef.current)
106
+ }
107
+ }
108
+ }, [isManualSyncing, isPaused, onPlaySegment, editedSegment, syncWordIndex])
109
+
50
110
  const handleKeyDown = useCallback((e: KeyboardEvent) => {
51
111
  if (e.code !== 'Space') return
52
112
 
@@ -60,14 +120,7 @@ export default function useManualSync({
60
120
  e.preventDefault()
61
121
  e.stopPropagation()
62
122
 
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
-
123
+ if (isManualSyncing && editedSegment && !isSpacebarPressed && !isPaused) {
71
124
  setIsSpacebarPressed(true)
72
125
 
73
126
  // Record the start time of the current word
@@ -80,18 +133,86 @@ export default function useManualSync({
80
133
  if (syncWordIndex < editedSegment.words.length) {
81
134
  const newWords = [...wordsRef.current]
82
135
  const currentWord = newWords[syncWordIndex]
136
+ const currentStartTime = currentTimeRef.current
83
137
 
84
138
  // Set the start time for the current word
85
- currentWord.start_time = currentTimeRef.current
139
+ currentWord.start_time = currentStartTime
140
+
141
+ // Handle the end time of the previous word (if it exists)
142
+ if (syncWordIndex > 0) {
143
+ const previousWord = newWords[syncWordIndex - 1]
144
+ if (previousWord.start_time !== null) {
145
+ const timeSincePreviousStart = currentStartTime - previousWord.start_time
146
+
147
+ // Only adjust previous word's end time if:
148
+ // 1. It doesn't have an end time set yet (was never released), OR
149
+ // 2. The current start would overlap with existing end time
150
+ const needsAdjustment = previousWord.end_time === null ||
151
+ (previousWord.end_time !== null && previousWord.end_time > currentStartTime)
152
+
153
+ if (needsAdjustment) {
154
+ if (timeSincePreviousStart > 1.0) {
155
+ // Gap of over 1 second - set previous word's end time to 500ms after its start
156
+ previousWord.end_time = previousWord.start_time + 0.5
157
+ console.log('useManualSync - Gap detected, setting previous word end time to +500ms', {
158
+ previousWordIndex: syncWordIndex - 1,
159
+ previousWordText: previousWord.text,
160
+ previousStartTime: previousWord.start_time,
161
+ previousEndTime: previousWord.end_time,
162
+ gap: timeSincePreviousStart.toFixed(2) + 's',
163
+ reason: 'gap > 1s'
164
+ })
165
+ } else {
166
+ // Normal flow - set previous word's end time to current word's start time minus 5ms
167
+ previousWord.end_time = currentStartTime - 0.005
168
+ console.log('useManualSync - Setting previous word end time to current start - 5ms', {
169
+ previousWordIndex: syncWordIndex - 1,
170
+ previousWordText: previousWord.text,
171
+ previousEndTime: previousWord.end_time,
172
+ currentStartTime: currentStartTime,
173
+ gap: timeSincePreviousStart.toFixed(2) + 's',
174
+ reason: 'normal flow'
175
+ })
176
+ }
177
+ } else {
178
+ console.log('useManualSync - Preserving previous word timing (manually set)', {
179
+ previousWordIndex: syncWordIndex - 1,
180
+ previousWordText: previousWord.text,
181
+ previousStartTime: previousWord.start_time,
182
+ previousEndTime: previousWord.end_time,
183
+ preservedDuration: previousWord.end_time !== null ?
184
+ (previousWord.end_time - previousWord.start_time).toFixed(2) + 's' : 'N/A',
185
+ reason: 'already timed correctly'
186
+ })
187
+ }
188
+ }
189
+ }
190
+
191
+ console.log('useManualSync - Recording word start time', {
192
+ wordIndex: syncWordIndex,
193
+ wordText: currentWord?.text,
194
+ time: currentStartTime
195
+ })
86
196
 
87
197
  // Update our ref
88
198
  wordsRef.current = newWords
89
199
 
90
- // Update the segment
91
- updateSegment(newWords)
200
+ // Mark that we need to update the segment
201
+ needsSegmentUpdateRef.current = true
92
202
  }
93
203
  } else if (!isManualSyncing && editedSegment && onPlaySegment) {
94
- console.log('useManualSync - Handling segment playback')
204
+ console.log('useManualSync - Handling segment playback', {
205
+ editedSegmentId: editedSegment.id,
206
+ isGlobalReplacement: editedSegment.id === 'global-replacement'
207
+ })
208
+
209
+ // For global replacement segments, don't handle general playback
210
+ // since we want the user to use Manual Sync instead
211
+ if (editedSegment.id === 'global-replacement') {
212
+ console.log('useManualSync - Ignoring playback for global replacement - please use Manual Sync')
213
+ return
214
+ }
215
+
95
216
  // Toggle segment playback when not in manual sync mode
96
217
  const startTime = editedSegment.start_time ?? 0
97
218
  const endTime = editedSegment.end_time ?? 0
@@ -104,7 +225,7 @@ export default function useManualSync({
104
225
  onPlaySegment(startTime)
105
226
  }
106
227
  }
107
- }, [isManualSyncing, editedSegment, syncWordIndex, onPlaySegment, updateSegment, isSpacebarPressed])
228
+ }, [isManualSyncing, editedSegment, syncWordIndex, onPlaySegment, isSpacebarPressed, isPaused])
108
229
 
109
230
  const handleKeyUp = useCallback((e: KeyboardEvent) => {
110
231
  if (e.code !== 'Space') return
@@ -120,7 +241,7 @@ export default function useManualSync({
120
241
  e.preventDefault()
121
242
  e.stopPropagation()
122
243
 
123
- if (isManualSyncing && editedSegment && isSpacebarPressed) {
244
+ if (isManualSyncing && editedSegment && isSpacebarPressed && !isPaused) {
124
245
  const currentWord = syncWordIndex < editedSegment.words.length ? editedSegment.words[syncWordIndex] : null
125
246
  const pressDuration = spacebarPressTimeRef.current ? Date.now() - spacebarPressTimeRef.current : 0
126
247
  const isTap = pressDuration < TAP_THRESHOLD_MS
@@ -132,6 +253,7 @@ export default function useManualSync({
132
253
  endTime: currentTimeRef.current,
133
254
  pressDuration: `${pressDuration}ms`,
134
255
  isTap,
256
+ tapThreshold: TAP_THRESHOLD_MS,
135
257
  duration: currentWord ? (currentTimeRef.current - (wordStartTimeRef.current || 0)).toFixed(2) + 's' : 'N/A'
136
258
  })
137
259
 
@@ -147,12 +269,20 @@ export default function useManualSync({
147
269
  const defaultEndTime = (wordStartTimeRef.current || currentTimeRef.current) + DEFAULT_WORD_DURATION
148
270
  currentWord.end_time = defaultEndTime
149
271
  console.log('useManualSync - Tap detected, setting default duration', {
272
+ wordText: currentWord.text,
273
+ startTime: wordStartTimeRef.current,
150
274
  defaultEndTime,
151
275
  duration: DEFAULT_WORD_DURATION
152
276
  })
153
277
  } else {
154
278
  // For a hold, use the current time as the end time
155
279
  currentWord.end_time = currentTimeRef.current
280
+ console.log('useManualSync - Hold detected, using actual timing', {
281
+ wordText: currentWord.text,
282
+ startTime: wordStartTimeRef.current,
283
+ endTime: currentTimeRef.current,
284
+ actualDuration: (currentTimeRef.current - (wordStartTimeRef.current || 0)).toFixed(2) + 's'
285
+ })
156
286
  }
157
287
 
158
288
  // Update our ref
@@ -176,11 +306,11 @@ export default function useManualSync({
176
306
  setSyncWordIndex(syncWordIndex + 1)
177
307
  }
178
308
 
179
- // Update the segment
180
- updateSegment(newWords)
309
+ // Mark that we need to update the segment
310
+ needsSegmentUpdateRef.current = true
181
311
  }
182
312
  }
183
- }, [isManualSyncing, editedSegment, syncWordIndex, updateSegment, isSpacebarPressed])
313
+ }, [isManualSyncing, editedSegment, syncWordIndex, isSpacebarPressed, isPaused])
184
314
 
185
315
  // Add a handler for when the next word starts to adjust previous word's end time if needed
186
316
  useEffect(() => {
@@ -208,11 +338,11 @@ export default function useManualSync({
208
338
  // Update our ref
209
339
  wordsRef.current = newWords
210
340
 
211
- // Update the segment
212
- updateSegment(newWords)
341
+ // Mark that we need to update the segment
342
+ needsSegmentUpdateRef.current = true
213
343
  }
214
344
  }
215
- }, [syncWordIndex, isManualSyncing, editedSegment, updateSegment])
345
+ }, [syncWordIndex, isManualSyncing, editedSegment])
216
346
 
217
347
  // Combine the key handlers into a single function for external use
218
348
  const handleSpacebar = useCallback((e: KeyboardEvent) => {
@@ -234,32 +364,70 @@ export default function useManualSync({
234
364
  // Make sure we have the latest words
235
365
  wordsRef.current = [...editedSegment.words]
236
366
 
367
+ // Find the first unsynced word to start from
368
+ const firstUnsyncedIndex = editedSegment.words.findIndex(word =>
369
+ word.start_time === null || word.end_time === null
370
+ )
371
+
372
+ const startIndex = firstUnsyncedIndex !== -1 ? firstUnsyncedIndex : 0
373
+
374
+ console.log('useManualSync - Starting manual sync', {
375
+ totalWords: editedSegment.words.length,
376
+ startingFromIndex: startIndex,
377
+ startingWord: editedSegment.words[startIndex]?.text
378
+ })
379
+
237
380
  setIsManualSyncing(true)
238
- setSyncWordIndex(0)
381
+ setSyncWordIndex(startIndex)
239
382
  setIsSpacebarPressed(false)
240
383
  wordStartTimeRef.current = null
241
384
  spacebarPressTimeRef.current = null
385
+ needsSegmentUpdateRef.current = false
242
386
  // Start playing 3 seconds before segment start
243
387
  onPlaySegment((editedSegment.start_time ?? 0) - 3)
244
388
  }, [isManualSyncing, editedSegment, onPlaySegment, cleanupManualSync])
245
389
 
246
- // Auto-stop sync if we go past the end time
390
+ // Auto-stop sync if we go past the end time (but not for global replacement segments)
247
391
  useEffect(() => {
248
- if (!editedSegment) return
392
+ if (!editedSegment || !isManualSyncing) return
249
393
 
250
- const endTime = editedSegment.end_time ?? 0
394
+ // Don't auto-stop for global replacement segments - let user manually finish
395
+ if (editedSegment.id === 'global-replacement') {
396
+ console.log('useManualSync - Skipping auto-stop for global replacement segment')
397
+ return
398
+ }
251
399
 
252
- if (window.isAudioPlaying && currentTimeRef.current > endTime) {
253
- console.log('Stopping playback: current time exceeded end time')
254
- window.toggleAudioPlayback?.()
255
- cleanupManualSync()
400
+ // Set up an interval to check if we should auto-stop
401
+ const checkAutoStop = () => {
402
+ const endTime = editedSegment.end_time ?? 0
403
+
404
+ if (window.isAudioPlaying && currentTimeRef.current > endTime) {
405
+ console.log('useManualSync - Auto-stopping: current time exceeded end time', {
406
+ currentTime: currentTimeRef.current,
407
+ endTime,
408
+ segmentId: editedSegment.id
409
+ })
410
+ window.toggleAudioPlayback?.()
411
+ cleanupManualSync()
412
+ }
413
+ }
414
+
415
+ // Check immediately and then every 100ms
416
+ checkAutoStop()
417
+ const intervalId = setInterval(checkAutoStop, 100)
418
+
419
+ return () => {
420
+ clearInterval(intervalId)
256
421
  }
257
- }, [isManualSyncing, editedSegment, currentTimeRef, cleanupManualSync])
422
+ }, [isManualSyncing, editedSegment, cleanupManualSync])
258
423
 
259
424
  return {
260
425
  isManualSyncing,
426
+ isPaused,
261
427
  syncWordIndex,
262
428
  startManualSync,
429
+ pauseManualSync,
430
+ resumeManualSync,
263
431
  cleanupManualSync,
264
432
  handleSpacebar,
265
433
  isSpacebarPressed
@@ -1 +1 @@
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/TimingOffsetModal.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/timingUtils.ts","./src/components/shared/utils/wordUtils.ts","./src/hooks/useManualSync.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/ReplaceAllLyricsModal.tsx","./src/components/ReviewChangesModal.tsx","./src/components/SegmentDetailsModal.tsx","./src/components/TimelineEditor.tsx","./src/components/TimingOffsetModal.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/timingUtils.ts","./src/components/shared/utils/wordUtils.ts","./src/hooks/useManualSync.ts","./src/types/global.d.ts"],"version":"5.6.3"}