lyrics-transcriber 0.68.0__py3-none-any.whl → 0.69.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.
@@ -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
 
@@ -265,11 +268,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
265
268
  index: number
266
269
  originalSegment: LyricsSegment
267
270
  } | 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)
271
+ const [isReplaceAllLyricsModalOpen, setIsReplaceAllLyricsModalOpen] = useState(false)
273
272
  const [isReviewModalOpen, setIsReviewModalOpen] = useState(false)
274
273
  const [currentAudioTime, setCurrentAudioTime] = useState(0)
275
274
  const [isUpdatingHandlers, setIsUpdatingHandlers] = useState(false)
@@ -397,11 +396,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
397
396
  isReviewModalOpen ||
398
397
  isAddLyricsModalOpen ||
399
398
  isFindReplaceModalOpen ||
400
- isEditAllModalOpen ||
399
+ isReplaceAllLyricsModalOpen ||
401
400
  isTimingOffsetModalOpen
402
401
  )
403
402
  setIsAnyModalOpen(modalOpen)
404
- }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen, isTimingOffsetModalOpen])
403
+ }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isReplaceAllLyricsModalOpen, isTimingOffsetModalOpen])
405
404
 
406
405
  // Calculate effective mode based on modifier key states
407
406
  const effectiveMode = isCtrlPressed ? 'delete_word' : (isShiftPressed ? 'highlight' : interactionMode)
@@ -783,212 +782,33 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
783
782
  updateDataWithHistory(newData, 'find/replace'); // Update history
784
783
  }
785
784
 
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
785
 
906
- // Get the updated words from the global segment
907
- const updatedWords = updatedSegment.words
908
786
 
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
916
-
917
- // Get the words for this segment from the updated global segment
918
- const segmentWords = []
919
- const endIndex = Math.min(wordIndex + originalWordCount, updatedWords.length)
920
-
921
- for (let i = wordIndex; i < endIndex; i++) {
922
- segmentWords.push(updatedWords[i])
923
- }
924
-
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
- }
946
- }
947
-
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
- }
787
+ // Add handler for Replace All Lyrics functionality
788
+ const handleReplaceAllLyrics = useCallback(() => {
789
+ console.log('ReplaceAllLyrics - Opening modal')
790
+ setIsReplaceAllLyricsModalOpen(true)
791
+ }, [])
972
792
 
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)
793
+ // Handle saving new segments from Replace All Lyrics
794
+ const handleSaveReplaceAllLyrics = useCallback((newSegments: LyricsSegment[]) => {
795
+ console.log('ReplaceAllLyrics - Saving new segments:', {
796
+ segmentCount: newSegments.length,
797
+ totalWords: newSegments.reduce((count, segment) => count + segment.words.length, 0)
978
798
  })
979
799
 
980
- // Update the data with the new segments
800
+ // Create new data with the replaced segments
981
801
  const newData = {
982
802
  ...data,
983
- corrected_segments: updatedSegments
984
- };
985
- updateDataWithHistory(newData, 'edit all'); // Update history
986
-
987
- // Close the modal
988
- setIsEditAllModalOpen(false)
989
- setGlobalEditSegment(null)
803
+ corrected_segments: newSegments
804
+ }
805
+
806
+ updateDataWithHistory(newData, 'replace all lyrics')
807
+ setIsReplaceAllLyricsModalOpen(false)
990
808
  }, [data, updateDataWithHistory])
991
809
 
810
+
811
+
992
812
  // Undo/Redo handlers
993
813
  const handleUndo = useCallback(() => {
994
814
  if (historyIndex > 0) {
@@ -1087,7 +907,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
1087
907
  isUpdatingHandlers={isUpdatingHandlers}
1088
908
  onHandlerClick={handleHandlerClick}
1089
909
  onFindReplace={() => setIsFindReplaceModalOpen(true)}
1090
- onEditAll={handleEditAll}
910
+ onEditAll={handleReplaceAllLyrics}
1091
911
  onTimingOffset={handleOpenTimingOffsetModal}
1092
912
  timingOffsetMs={timingOffsetMs}
1093
913
  onUndo={handleUndo}
@@ -1162,38 +982,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
1162
982
  </Grid>
1163
983
  </Grid>
1164
984
 
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
- />
985
+
1197
986
 
1198
987
  <EditModal
1199
988
  open={Boolean(editModalSegment)}
@@ -1266,6 +1055,15 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
1266
1055
  currentOffset={timingOffsetMs}
1267
1056
  onApply={handleApplyTimingOffset}
1268
1057
  />
1058
+
1059
+ <ReplaceAllLyricsModal
1060
+ open={isReplaceAllLyricsModalOpen}
1061
+ onClose={() => setIsReplaceAllLyricsModalOpen(false)}
1062
+ onSave={handleSaveReplaceAllLyrics}
1063
+ onPlaySegment={handlePlaySegment}
1064
+ currentTime={currentAudioTime}
1065
+ setModalSpacebarHandler={handleSetModalSpacebarHandler}
1066
+ />
1269
1067
  </Box>
1270
1068
  )
1271
1069
  }
@@ -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}