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
@@ -16,6 +16,7 @@ import TranscriptionView from './TranscriptionView'
16
16
  import { WordClickInfo, FlashType } from './shared/types'
17
17
  import EditModal from './EditModal'
18
18
  import ReviewChangesModal from './ReviewChangesModal'
19
+ import ReplaceAllLyricsModal from './ReplaceAllLyricsModal'
19
20
  import {
20
21
  addSegmentBefore,
21
22
  splitSegment,
@@ -39,6 +40,8 @@ declare global {
39
40
  interface Window {
40
41
  toggleAudioPlayback?: () => void;
41
42
  seekAndPlayAudio?: (startTime: number) => void;
43
+ getAudioDuration?: () => number;
44
+ isAudioPlaying?: boolean;
42
45
  }
43
46
  }
44
47
 
@@ -193,6 +196,7 @@ interface MemoizedHeaderProps {
193
196
  onRedo: () => void
194
197
  canUndo: boolean
195
198
  canRedo: boolean
199
+ onUnCorrectAll: () => void
196
200
  }
197
201
 
198
202
  // Create a memoized Header component
@@ -216,7 +220,8 @@ const MemoizedHeader = memo(function MemoizedHeader({
216
220
  onUndo,
217
221
  onRedo,
218
222
  canUndo,
219
- canRedo
223
+ canRedo,
224
+ onUnCorrectAll
220
225
  }: MemoizedHeaderProps) {
221
226
  return (
222
227
  <Header
@@ -240,6 +245,7 @@ const MemoizedHeader = memo(function MemoizedHeader({
240
245
  onRedo={onRedo}
241
246
  canUndo={canUndo}
242
247
  canRedo={canRedo}
248
+ onUnCorrectAll={onUnCorrectAll}
243
249
  />
244
250
  );
245
251
  });
@@ -265,11 +271,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
265
271
  index: number
266
272
  originalSegment: LyricsSegment
267
273
  } | null>(null)
268
- const [isEditAllModalOpen, setIsEditAllModalOpen] = useState(false)
269
- const [globalEditSegment, setGlobalEditSegment] = useState<LyricsSegment | null>(null)
270
- const [originalGlobalSegment, setOriginalGlobalSegment] = useState<LyricsSegment | null>(null)
271
- const [originalTranscribedGlobalSegment, setOriginalTranscribedGlobalSegment] = useState<LyricsSegment | null>(null)
272
- const [isLoadingGlobalEdit, setIsLoadingGlobalEdit] = useState(false)
274
+ const [isReplaceAllLyricsModalOpen, setIsReplaceAllLyricsModalOpen] = useState(false)
273
275
  const [isReviewModalOpen, setIsReviewModalOpen] = useState(false)
274
276
  const [currentAudioTime, setCurrentAudioTime] = useState(0)
275
277
  const [isUpdatingHandlers, setIsUpdatingHandlers] = useState(false)
@@ -397,11 +399,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
397
399
  isReviewModalOpen ||
398
400
  isAddLyricsModalOpen ||
399
401
  isFindReplaceModalOpen ||
400
- isEditAllModalOpen ||
402
+ isReplaceAllLyricsModalOpen ||
401
403
  isTimingOffsetModalOpen
402
404
  )
403
405
  setIsAnyModalOpen(modalOpen)
404
- }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen, isTimingOffsetModalOpen])
406
+ }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isReplaceAllLyricsModalOpen, isTimingOffsetModalOpen])
405
407
 
406
408
  // Calculate effective mode based on modifier key states
407
409
  const effectiveMode = isCtrlPressed ? 'delete_word' : (isShiftPressed ? 'highlight' : interactionMode)
@@ -783,212 +785,54 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
783
785
  updateDataWithHistory(newData, 'find/replace'); // Update history
784
786
  }
785
787
 
786
- // Add handler for Edit All functionality
787
- const handleEditAll = useCallback(() => {
788
- console.log('EditAll - Starting process');
789
-
790
- // Create empty placeholder segments to prevent the modal from closing
791
- const placeholderSegment: LyricsSegment = {
792
- id: 'loading-placeholder',
793
- words: [],
794
- text: '',
795
- start_time: 0,
796
- end_time: 1
797
- };
798
-
799
- // Set placeholder segments first
800
- setGlobalEditSegment(placeholderSegment);
801
- setOriginalGlobalSegment(placeholderSegment);
802
-
803
- // Show loading state
804
- setIsLoadingGlobalEdit(true);
805
- console.log('EditAll - Set loading state to true');
806
-
807
- // Open the modal with placeholder data
808
- setIsEditAllModalOpen(true);
809
- console.log('EditAll - Set modal open to true');
810
-
811
- // Use requestAnimationFrame to ensure the modal with loading state is rendered
812
- // before doing the expensive operation
813
- requestAnimationFrame(() => {
814
- console.log('EditAll - Inside requestAnimationFrame');
815
-
816
- // Use setTimeout to allow the modal to render before doing the expensive operation
817
- setTimeout(() => {
818
- console.log('EditAll - Inside setTimeout, starting data processing');
819
-
820
- try {
821
- console.time('EditAll - Data processing');
822
-
823
- // Create a combined segment with all words from all segments
824
- const allWords = data.corrected_segments.flatMap(segment => segment.words)
825
- console.log(`EditAll - Collected ${allWords.length} words from all segments`);
826
-
827
- // Sort words by start time to maintain chronological order
828
- const sortedWords = [...allWords].sort((a, b) => {
829
- const aTime = a.start_time ?? 0
830
- const bTime = b.start_time ?? 0
831
- return aTime - bTime
832
- })
833
- console.log('EditAll - Sorted words by start time');
834
-
835
- // Create a global segment containing all words
836
- const globalSegment: LyricsSegment = {
837
- id: 'global-edit',
838
- words: sortedWords,
839
- text: sortedWords.map(w => w.text).join(' '),
840
- start_time: sortedWords[0]?.start_time ?? null,
841
- end_time: sortedWords[sortedWords.length - 1]?.end_time ?? null
842
- }
843
- console.log('EditAll - Created global segment');
844
-
845
- // Store the original global segment for reset functionality
846
- setGlobalEditSegment(globalSegment)
847
- console.log('EditAll - Set global edit segment');
848
-
849
- setOriginalGlobalSegment(JSON.parse(JSON.stringify(globalSegment)))
850
- console.log('EditAll - Set original global segment');
851
-
852
- // Create the original transcribed global segment for Un-Correct functionality
853
- if (originalData.original_segments) {
854
- console.log('EditAll - Processing original segments for Un-Correct functionality');
855
-
856
- // Get all words from original segments
857
- const originalWords = originalData.original_segments.flatMap((segment: LyricsSegment) => segment.words)
858
- console.log(`EditAll - Collected ${originalWords.length} words from original segments`);
859
-
860
- // Sort words by start time
861
- const sortedOriginalWords = [...originalWords].sort((a, b) => {
862
- const aTime = a.start_time ?? 0
863
- const bTime = b.start_time ?? 0
864
- return aTime - bTime
865
- })
866
- console.log('EditAll - Sorted original words by start time');
867
-
868
- // Create the original transcribed global segment
869
- const originalTranscribedGlobal: LyricsSegment = {
870
- id: 'original-transcribed-global',
871
- words: sortedOriginalWords,
872
- text: sortedOriginalWords.map(w => w.text).join(' '),
873
- start_time: sortedOriginalWords[0]?.start_time ?? null,
874
- end_time: sortedOriginalWords[sortedOriginalWords.length - 1]?.end_time ?? null
875
- }
876
- console.log('EditAll - Created original transcribed global segment');
877
-
878
- setOriginalTranscribedGlobalSegment(originalTranscribedGlobal)
879
- console.log('EditAll - Set original transcribed global segment');
880
- } else {
881
- setOriginalTranscribedGlobalSegment(null)
882
- console.log('EditAll - No original segments found, set original transcribed global segment to null');
883
- }
884
-
885
- console.timeEnd('EditAll - Data processing');
886
- } catch (error) {
887
- console.error('Error preparing global edit data:', error);
888
- } finally {
889
- // Clear loading state
890
- console.log('EditAll - Finished processing, setting loading state to false');
891
- setIsLoadingGlobalEdit(false);
892
- }
893
- }, 100); // Small delay to allow the modal to render
894
- });
895
- }, [data.corrected_segments, originalData.original_segments])
896
-
897
- // Handle saving the global edit
898
- const handleSaveGlobalEdit = useCallback((updatedSegment: LyricsSegment) => {
899
- console.log('Global Edit - Saving with new approach:', {
900
- updatedSegmentId: updatedSegment.id,
901
- wordCount: updatedSegment.words.length,
902
- originalSegmentCount: data.corrected_segments.length,
903
- originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
904
- })
905
-
906
- // Get the updated words from the global segment
907
- const updatedWords = updatedSegment.words
908
-
909
- // Create a new array of segments with the same structure as the original
910
- const updatedSegments = []
911
- let wordIndex = 0
912
-
913
- // Distribute words to segments based on the original segment sizes
914
- for (const segment of data.corrected_segments) {
915
- const originalWordCount = segment.words.length
788
+ // Add handler for Un-Correct All functionality
789
+ const handleUnCorrectAll = useCallback(() => {
790
+ if (!originalData.original_segments) {
791
+ console.warn('No original segments available for un-correcting')
792
+ return
793
+ }
916
794
 
917
- // Get the words for this segment from the updated global segment
918
- const segmentWords = []
919
- const endIndex = Math.min(wordIndex + originalWordCount, updatedWords.length)
795
+ if (window.confirm('Are you sure you want to revert all segments to their original transcribed state? This will undo all corrections made.')) {
796
+ console.log('Un-Correct All: Reverting all segments to original transcribed state', {
797
+ originalSegmentCount: originalData.original_segments.length,
798
+ currentSegmentCount: data.corrected_segments.length
799
+ })
920
800
 
921
- for (let i = wordIndex; i < endIndex; i++) {
922
- segmentWords.push(updatedWords[i])
801
+ // Create new data with original segments as corrected segments
802
+ const newData: CorrectionData = {
803
+ ...data,
804
+ corrected_segments: JSON.parse(JSON.stringify(originalData.original_segments))
923
805
  }
924
806
 
925
- // Update the word index for the next segment
926
- wordIndex = endIndex
927
-
928
- // If we have words for this segment, create an updated segment
929
- if (segmentWords.length > 0) {
930
- // Recalculate segment start and end times
931
- const validStartTimes = segmentWords.map(w => w.start_time).filter((t): t is number => t !== null)
932
- const validEndTimes = segmentWords.map(w => w.end_time).filter((t): t is number => t !== null)
933
-
934
- const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null
935
- const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null
936
-
937
- // Create the updated segment
938
- updatedSegments.push({
939
- ...segment,
940
- words: segmentWords,
941
- text: segmentWords.map(w => w.text).join(' '),
942
- start_time: segmentStartTime,
943
- end_time: segmentEndTime
944
- })
945
- }
807
+ updateDataWithHistory(newData, 'un-correct all segments')
946
808
  }
809
+ }, [originalData.original_segments, data, updateDataWithHistory])
947
810
 
948
- // If there are any remaining words, add them to the last segment
949
- if (wordIndex < updatedWords.length) {
950
- const remainingWords = updatedWords.slice(wordIndex)
951
- const lastSegment = updatedSegments[updatedSegments.length - 1]
952
-
953
- // Combine the remaining words with the last segment
954
- const combinedWords = [...lastSegment.words, ...remainingWords]
955
-
956
- // Recalculate segment start and end times
957
- const validStartTimes = combinedWords.map(w => w.start_time).filter((t): t is number => t !== null)
958
- const validEndTimes = combinedWords.map(w => w.end_time).filter((t): t is number => t !== null)
959
-
960
- const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null
961
- const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null
962
-
963
- // Update the last segment
964
- updatedSegments[updatedSegments.length - 1] = {
965
- ...lastSegment,
966
- words: combinedWords,
967
- text: combinedWords.map(w => w.text).join(' '),
968
- start_time: segmentStartTime,
969
- end_time: segmentEndTime
970
- }
971
- }
811
+ // Add handler for Replace All Lyrics functionality
812
+ const handleReplaceAllLyrics = useCallback(() => {
813
+ console.log('ReplaceAllLyrics - Opening modal')
814
+ setIsReplaceAllLyricsModalOpen(true)
815
+ }, [])
972
816
 
973
- console.log('Global Edit - Updated Segments with new approach:', {
974
- segmentCount: updatedSegments.length,
975
- firstSegmentWordCount: updatedSegments[0]?.words.length,
976
- totalWordCount: updatedSegments.reduce((count, segment) => count + segment.words.length, 0),
977
- originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
817
+ // Handle saving new segments from Replace All Lyrics
818
+ const handleSaveReplaceAllLyrics = useCallback((newSegments: LyricsSegment[]) => {
819
+ console.log('ReplaceAllLyrics - Saving new segments:', {
820
+ segmentCount: newSegments.length,
821
+ totalWords: newSegments.reduce((count, segment) => count + segment.words.length, 0)
978
822
  })
979
823
 
980
- // Update the data with the new segments
824
+ // Create new data with the replaced segments
981
825
  const newData = {
982
826
  ...data,
983
- corrected_segments: updatedSegments
984
- };
985
- updateDataWithHistory(newData, 'edit all'); // Update history
986
-
987
- // Close the modal
988
- setIsEditAllModalOpen(false)
989
- setGlobalEditSegment(null)
827
+ corrected_segments: newSegments
828
+ }
829
+
830
+ updateDataWithHistory(newData, 'replace all lyrics')
831
+ setIsReplaceAllLyricsModalOpen(false)
990
832
  }, [data, updateDataWithHistory])
991
833
 
834
+
835
+
992
836
  // Undo/Redo handlers
993
837
  const handleUndo = useCallback(() => {
994
838
  if (historyIndex > 0) {
@@ -1087,13 +931,14 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
1087
931
  isUpdatingHandlers={isUpdatingHandlers}
1088
932
  onHandlerClick={handleHandlerClick}
1089
933
  onFindReplace={() => setIsFindReplaceModalOpen(true)}
1090
- onEditAll={handleEditAll}
934
+ onEditAll={handleReplaceAllLyrics}
1091
935
  onTimingOffset={handleOpenTimingOffsetModal}
1092
936
  timingOffsetMs={timingOffsetMs}
1093
937
  onUndo={handleUndo}
1094
938
  onRedo={handleRedo}
1095
939
  canUndo={canUndo}
1096
940
  canRedo={canRedo}
941
+ onUnCorrectAll={handleUnCorrectAll}
1097
942
  />
1098
943
 
1099
944
  <Grid container direction={isMobile ? 'column' : 'row'}>
@@ -1162,38 +1007,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
1162
1007
  </Grid>
1163
1008
  </Grid>
1164
1009
 
1165
- <EditModal
1166
- open={isEditAllModalOpen}
1167
- onClose={() => {
1168
- setIsEditAllModalOpen(false)
1169
- setGlobalEditSegment(null)
1170
- setOriginalGlobalSegment(null)
1171
- setOriginalTranscribedGlobalSegment(null)
1172
- handleSetModalSpacebarHandler(undefined)
1173
- }}
1174
- segment={globalEditSegment ?
1175
- timingOffsetMs !== 0 ?
1176
- applyOffsetToSegment(globalEditSegment, timingOffsetMs) :
1177
- globalEditSegment :
1178
- null}
1179
- segmentIndex={null}
1180
- originalSegment={originalGlobalSegment ?
1181
- timingOffsetMs !== 0 ?
1182
- applyOffsetToSegment(originalGlobalSegment, timingOffsetMs) :
1183
- originalGlobalSegment :
1184
- null}
1185
- onSave={handleSaveGlobalEdit}
1186
- onPlaySegment={handlePlaySegment}
1187
- currentTime={currentAudioTime}
1188
- setModalSpacebarHandler={handleSetModalSpacebarHandler}
1189
- originalTranscribedSegment={originalTranscribedGlobalSegment ?
1190
- timingOffsetMs !== 0 ?
1191
- applyOffsetToSegment(originalTranscribedGlobalSegment, timingOffsetMs) :
1192
- originalTranscribedGlobalSegment :
1193
- null}
1194
- isGlobal={true}
1195
- isLoading={isLoadingGlobalEdit}
1196
- />
1010
+
1197
1011
 
1198
1012
  <EditModal
1199
1013
  open={Boolean(editModalSegment)}
@@ -1266,6 +1080,15 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
1266
1080
  currentOffset={timingOffsetMs}
1267
1081
  onApply={handleApplyTimingOffset}
1268
1082
  />
1083
+
1084
+ <ReplaceAllLyricsModal
1085
+ open={isReplaceAllLyricsModalOpen}
1086
+ onClose={() => setIsReplaceAllLyricsModalOpen(false)}
1087
+ onSave={handleSaveReplaceAllLyrics}
1088
+ onPlaySegment={handlePlaySegment}
1089
+ currentTime={currentAudioTime}
1090
+ setModalSpacebarHandler={handleSetModalSpacebarHandler}
1091
+ />
1269
1092
  </Box>
1270
1093
  )
1271
1094
  }
@@ -174,12 +174,36 @@ export default function ReferenceView({
174
174
  navigator.clipboard.writeText(text);
175
175
  };
176
176
 
177
+ // Helper function to copy all reference text from current source
178
+ const copyAllReferenceText = () => {
179
+ const allText = currentSourceSegments
180
+ .map(segment => segment.words.map(w => w.text).join(' '))
181
+ .join('\n');
182
+ copyToClipboard(allText);
183
+ };
184
+
177
185
  return (
178
186
  <Paper sx={{ p: 0.8, position: 'relative' }}>
179
187
  <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
180
- <Typography variant="h6" sx={{ fontSize: '0.9rem', mb: 0 }}>
181
- Reference Lyrics
182
- </Typography>
188
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
189
+ <Typography variant="h6" sx={{ fontSize: '0.9rem', mb: 0 }}>
190
+ Reference Lyrics
191
+ </Typography>
192
+ <IconButton
193
+ size="small"
194
+ onClick={copyAllReferenceText}
195
+ sx={{
196
+ padding: '2px',
197
+ height: '20px',
198
+ width: '20px',
199
+ minHeight: '20px',
200
+ minWidth: '20px'
201
+ }}
202
+ title="Copy all reference lyrics"
203
+ >
204
+ <ContentCopyIcon sx={{ fontSize: '1rem' }} />
205
+ </IconButton>
206
+ </Box>
183
207
  <SourceSelector
184
208
  availableSources={availableSources}
185
209
  currentSource={effectiveCurrentSource}